By now, we have a working deploy.yml
Ansible playbook that deploys our Flask app to our server. But running this manually every time we push a change isn’t very DevOps, is it?
In this part of the series, we’ll automate deployments using GitHub Actions so that every push to the main
branch deploys our latest version automatically.
📁 Step 1: Create the GitHub Actions Workflow
First, create the folder to hold our CI/CD config:
mkdir .github/workflows/
Then create the workflow file at .github/workflows/deploy.yml
.
📝 GitHub Actions Configuration
name: Deploy Flask App
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 🛎️ Checkout repository
uses: actions/checkout@v3
- name: 📂 Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
# Preload host fingerprint to avoid prompt
ssh-keyscan -f inventory >> ~/.ssh/known_hosts
- name: 🚀 Run Ansible Playbook
uses: dawidd6/action-ansible-playbook@v2
with:
playbook: deploy.yml
inventory: inventory
private_key: ${{ secrets.SSH_PRIVATE_KEY }}
📚 What’s Happening Here?
- We check out the repository using GitHub’s official checkout action.
- We load the SSH private key from GitHub Secrets and add it to
~/.ssh/id_rsa
. - We dynamically load the host fingerprint from our
inventory
file usingssh-keyscan
. - Finally, we run the Ansible playbook using a prebuilt action.
🤔 Why Not Just Use known_hosts
in the Action?
You might wonder: “Can’t we just use the known_hosts:
parameter instead of doing that SSH setup manually?”
Yes, we could use a simpler setup like this:
name: Deploy Flask App
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: 🛎️ Checkout repository
uses: actions/checkout@v3
- name: 🚀 Run Ansible Playbook
uses: dawidd6/action-ansible-playbook@v2
with:
playbook: deploy.yml
inventory: inventory
private_key: ${{ secrets.SSH_PRIVATE_KEY }}
known_hosts: |
your.droplet.ip
This works fine—but you need to manually specify the IP address of your server.
Unfortunately, GitHub Actions doesn’t support things like $(cat inventory)
inside the with:
block, and you can’t inject it from a shell command. So while it’s shorter, it’s also less flexible—especially if you rebuild your droplet or rotate IPs often.
Personally, I prefer the first approach since it adapts automatically to the inventory
file.
🔐 Creating a GitHub Actions SSH Key
We’ll need a dedicated SSH key that GitHub Actions can use to connect to your server. To generate one:
ssh-keygen -t ed25519 -f ci_key -N "" -C "github-actions-deploy-key"
Explanation:
-N ""
→ no passphrase-C
→ gives the key a clear labelci_key
is the private key you’ll upload to GitHubci_key.pub
is what you add to your server
🔐 Optionally: Remove the Passphrase From an Existing Key
If you’d rather not create a new SSH key just for GitHub Actions, you can reuse an existing key—you’ll just need to make sure it doesn’t have a passphrase, since GitHub Actions can’t handle interactive prompts.
To remove the passphrase:
ssh-keygen -p -f ~/.ssh/id_rsa
# Press Enter when asked for a new passphrase (i.e. leave it empty)
🟡 Caution: This will affect all tools or scripts that rely on that key, so make sure it’s safe to do.
✅ Why a separate key is better:
- Easier to revoke just the CI access without affecting your personal SSH config
- Helps keep responsibilities and audit trails separate
- Avoids accidental lockouts or security risks
🔐 Add the Key to Your Server
You’ll need to add the new public key to your droplet’s authorized keys.
If you’re using the same setup from earlier parts of the series, you can do this by updating create-sudo-user.yml
(or creating a new playbook):
- name: add SSH key for github actions
authorized_key:
user: admin
state: present
key: "{{ lookup('file', '~/.ssh/ci_key.pub') }}"
path: "/home/admin/.ssh/authorized_keys"
🔒 Add the Private Key to GitHub Secrets
Go to:
GitHub > Repo > Settings > Secrets and variables > Actions
Create a new secret named:
SSH_PRIVATE_KEY
Paste in the full contents of your ci_key
file (not the .pub
one).
🚀 Triggering a Deploy
To test the pipeline, make a small change in your app. For example:
@app.route('/')
def hello():
return "Deployed from GitHub Actions!"
Commit and push the change to the main
branch:
git commit -am "Add github actions"
git push
Then open the Actions tab in GitHub to watch the workflow run.
Once complete, you can test your server:
curl http://<your-droplet-ip>
✅ You should see:
Deployed from GitHub Actions!
✅ Summary
In this article, we:
- Created a GitHub Actions workflow to automate Ansible deployments
- Configured SSH access securely using GitHub Secrets
- Set up a dedicated SSH key for CI use
- Automatically deployed our Flask app by pushing to
main
With this in place, we now have a fully automated deployment pipeline—just push your code and let GitHub handle the rest 💥
🔜 Next Up…
In the next part of the series, we’ll install MySQL and refactor our app to use a real database.
See you there! 🐬
Leave a Reply