I need to implement a zero-downtime CI/CD deployment pipeline for my Django API on an on-premises Linux server to ensure seamless updates without interrupting active users, similar to the previous maintenance-free setup.
You may want to try :
Deploy to a new directory -> Update symlink (current
) -> Gracefully reload Gunicorn (kill -HUP
) -> Keep Nginx active as a reverse proxy
Example:
1. Use a Versioned Directory Structure - Deploy each version into its own timestamped folder:
/var/www/myproject/
├── current -> /var/www/myproject/releases/2025-05-06-1230/
├── releases/
│ ├── 2025-05-06-1230/
│ ├── 2025-05-05-1700/
├── shared/ (media/, static/, .env etc.)
In your CI/CD pipeline (e.g., GitLab CI, Jenkins, or Bash script):
# Timestamped release path
RELEASE_DIR="/var/www/myproject/releases/$(date +%Y-%m-%d-%H%M)"
mkdir -p $RELEASE_DIR
# Clone or copy project files
git clone https://your-repo.git $RELEASE_DIR
cd $RELEASE_DIR
# Setup virtual environment
source /var/www/myproject/venv/bin/activate
# Install dependencies
pip install -r requirements.txt
# Migrate DB and collect static
python manage.py migrate --noinput
python manage.py collectstatic --noinput
# Point "current" symlink to new release
ln -sfn $RELEASE_DIR /var/www/myproject/current
# Reload Gunicorn gracefully
kill -HUP `cat /run/gunicorn.pid`
Use Gunicorn's --pid
flag and HUP
signal to gracefully reload:
gunicorn --pid /run/gunicorn.pid ...
kill -HUP `cat /run/gunicorn.pid`
Use Nginx to proxy requests to Gunicorn, which adds buffering and reliability:
location / {
proxy_pass http://unix:/run/gunicorn.sock;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}