DEV Community

Sospeter Mong'are
Sospeter Mong'are

Posted on

How to Set Up CI/CD for Your Django App Using GitHub Actions and Systemd

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Reload and enable it:

sudo systemctl daemon-reload sudo systemctl enable django_app sudo systemctl start django_app 
Enter fullscreen mode Exit fullscreen mode

Check status:

sudo systemctl status django_app 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Add this line:

deployuser ALL=(ALL) NOPASSWD: /bin/systemctl restart django_app 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Step 5: Push Code and Watch the Magic

Now every time you push to the main branch, GitHub will:

  1. SSH into your server
  2. Pull the latest code
  3. Install dependencies
  4. Run database migrations
  5. Collect static files
  6. Restart Gunicorn

You can view workflow progress under Actions in your GitHub repo.


Bonus: Check Logs

To debug:

journalctl -u django_app 
Enter fullscreen mode Exit fullscreen mode

🎉 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.

Top comments (0)