Setting up continuous deployment for your Django application can save you hours of manual server updates. In this guide, we’ll walk through how to automate deployment using GitHub Actions, SSH, and Systemd.
We’ll use a real-world example: a Django backend API hosted on a subdomain (api.example.com
), deployed to a remote Ubuntu server.
Prerequisites
Before you begin, ensure you have:
- A Django project hosted on GitHub.
-
A remote server (Ubuntu) with:
- Python & pip
virtualenv
- Gunicorn
- Nginx configured for your app
SSH access to the server
Gunicorn configured as a systemd service
GitHub repository secrets setup
Step 1: Create a Gunicorn Systemd Service
On your server, create a systemd service for Gunicorn:
sudo nano /etc/systemd/system/django_app.service
Paste this config:
[Unit]
Description=Gunicorn daemon for django_app
After=network.target
[Service]
User=deployuser
Group=www-data
WorkingDirectory=/var/www/django_app
ExecStart=/var/www/django_app/venv/bin/gunicorn \
--access-logfile - \
--workers 3 \
--bind unix:/var/www/django_app/django_app.sock \
project_folder.wsgi:application
Restart=always
[Install]
WantedBy=multi-user.target
Reload and enable it:
sudo systemctl daemon-reload
sudo systemctl enable django_app
sudo systemctl start django_app
Check status:
sudo systemctl status django_app
Step 2: Add Sudo Access Without Password
Give the deployment user (deployuser
) permission to restart Gunicorn without a password:
sudo visudo -f /etc/sudoers.d/deployuser
Add this line:
deployuser ALL=(ALL) NOPASSWD: /bin/systemctl restart django_app
Step 3: Add Secrets to GitHub Repository
Go to Settings > Secrets and variables > Actions > New repository secret and add:
Name | Value |
---|---|
SSH_PRIVATE_KEY |
Your private key for SSH |
SSH_USER |
The user (e.g., deployuser ) |
SSH_HOST |
Your server's IP or domain |
PROJECT_PATH |
e.g., /var/www/django_app
|
🧪 Step 4: Add the GitHub Actions CI/CD Workflow
Create .github/workflows/deploy.yml
in your repo:
name: Deploy Django backend api to Server
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Set up SSH
uses: webfactory/ssh-agent@v0.7.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Deploy to Server
run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} << 'EOF'
cd ${{ secrets.PROJECT_PATH }}
git pull origin main
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate --noinput
python manage.py collectstatic --noinput
sudo systemctl restart django_app
EOF
Step 5: Push Code and Watch the Magic
Now every time you push to the main
branch, GitHub will:
- SSH into your server
- Pull the latest code
- Install dependencies
- Run database migrations
- Collect static files
- Restart Gunicorn
You can view workflow progress under Actions in your GitHub repo.
Bonus: Check Logs
To debug:
journalctl -u django_app
🎉 Conclusion
With GitHub Actions + Gunicorn + systemd, you now have a robust deployment pipeline that:
- Updates code automatically
- Handles migrations and static files
- Restarts the app with zero manual work
Let me know if you'd like a companion article on setting this up with Docker, GitLab CI, or for frontend apps too.