Now that everything is up and running, it’s time to add a simple deployment strategy using Ansible. This will allow us to push new versions of our app with a single command—and later we’ll automate it further using CI/CD.
Let’s walk through how to:
- Restart Gunicorn automatically after a deploy
- Structure an Ansible-based deployment
- Deploy a Flask app from a GitHub repo
- Set up versioned releases with symlinks
- Restart Gunicorn automatically after a deploy
📄 Step 1: Create the Deployment Playbook
Inside your main ansible/
directory (next to main.yml
), create a file named deploy.yml
.
- name: Deploy Flask App
hosts: digitalocean
become: true
vars_files:
- vars.yml
remote_user: "{{ username }}"
tasks:
- name: Generate timestamp
command: date +"%Y-%m-%d-%H-%M-%S"
register: timestamp
changed_when: false
- name: Set deployment paths
set_fact:
deploy_dir: "{{ home_dir }}/{{ timestamp.stdout }}"
current_dir: "{{ home_dir }}/current"
- name: Clone the latest repository
git:
repo: "https://github.com/{{ git_repo }}.git"
dest: "{{ deploy_dir }}"
version: main
- name: Set up Python virtual environment
command: python3 -m venv venv
args:
chdir: "{{ deploy_dir }}/app"
- name: Install dependencies
pip:
requirements: "{{ deploy_dir }}/app/requirements.txt"
virtualenv: "{{ deploy_dir }}/app/venv"
- name: Update symlink for current deployment
file:
src: "{{ deploy_dir }}/app"
dest: "{{ current_dir }}"
state: link
force: yes
- name: Restart Gunicorn
systemd:
name: "{{ service_name }}"
state: restarted
daemon_reload: true
🔍 Breaking It Down
🔧 vars_files
vars_files:
- vars.yml
We’ve extracted shared variables (like username
, home_dir
, and service_name
) to a vars.yml
file. This keeps things DRY and makes variables reusable across main.yml
and deploy.yml
.
Make sure to update main.yml
to include the same vars_files:
block if you haven’t already.
🕒 Generate Timestamp & Deployment Paths
tasks:
- name: Generate timestamp
command: date +"%Y-%m-%d-%H-%M-%S"
register: timestamp
changed_when: false
- name: Set deployment paths
set_fact:
deploy_dir: "{{ home_dir }}/{{ timestamp.stdout }}"
current_dir: "{{ home_dir }}/current"
- We create a timestamp to give each deploy its own unique folder.
- This enables versioned deployments, and switching versions is as simple as updating a symlink.
📦 Clone Repo & Install Dependencies
- name: Clone the latest repository
git:
repo: "https://github.com/{{ git_repo }}.git"
dest: "{{ deploy_dir }}"
version: main
- name: Set up Python virtual environment
command: python3 -m venv venv
args:
chdir: "{{ deploy_dir }}/app"
- name: Install dependencies
pip:
requirements: "{{ deploy_dir }}/app/requirements.txt"
virtualenv: "{{ deploy_dir }}/app/venv"
- We clone your app from GitHub into the
deploy_dir
. - A virtual environment is created inside the cloned app folder.
- Dependencies from
requirements.txt
are installed using Ansible’spip
module.
🔗 Update Symlink & Restart Gunicorn
- name: Update symlink for current deployment
file:
src: "{{ deploy_dir }}/app"
dest: "{{ current_dir }}"
state: link
force: yes
- name: Restart Gunicorn
systemd:
name: "{{ service_name }}"
state: restarted
daemon_reload: true
- We update the
current
symlink to point to the new release folder. - Then we restart Gunicorn to pick up the changes.
This approach is simple, repeatable, and allows rollback by just repointing the current
symlink.
📁 Step 2: Create the Flask App Repository
In the parent directory:
cd ..
mkdir app
cd app
requirements.txt
flask
gunicorn
wsgi.py
from src.app import app
if __name__ == "__main__":
app.run()
src/app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return "Deployed with Ansible!"
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8000)
🔁 Step 3: Initialize the Git Repo
Login to GitHub and create a new private or public repository for your app.
Then, on the command line:
git init
git remote add origin ssh://<your repo>.git
Add a .gitignore
file:
*.tfstate
*.tfstate.*
.terraform
🚨 Also make sure not to commit any
.pem
,.key
,.env
, or other sensitive files.
Now push the app:
git add .
git commit -m "Initial commit"
git push --set-upstream origin $(git_current_branch) # or "gpsup" if you're using zsh
🚀 Step 4: Run the Deploy Script
With your GitHub repo set up and pushed, you can now deploy:
play deploy.yml
Once the playbook finishes, test the app:
curl http://<droplet ip>
✅ You should see:
Deployed with Ansible!
✅ Summary
In this part, we:
- Created a separate Ansible deployment playbook
- Implemented timestamped deployments using symlinks
- Cloned our app from GitHub and installed dependencies
- Restarted Gunicorn to serve the new release
This gives us a solid, repeatable deployment process—no manual steps required!
🔜 Next Up…
In the next part of the series, we’ll hook this up to GitHub Actions so that every push to your repo automatically triggers a deployment—zero clicks required.
See you there 👋
Leave a Reply