Introduction
Preparing the Environment
Load .env only in development; production reads from OS env
if os.getenv('ENV') != 'production': environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
SECRET_KEY = env('SECRET_KEY') DEBUG = env.bool('DEBUG', default=False) ALLOWED_HOSTS = env.list('ALLOWED_HOSTS') </code></pre>
<h3>3. Configuring the Database</h3> <p>Leverage <code>dj‑database‑url</code> to parse the database URL defined in <code>.env</code>:</p> <pre><code>import dj_database_url DATABASES = { 'default': dj_database_url.config(conn_max_age=600) } </code></pre>Configuring Django for Production
SECURE_SSL_REDIRECT = True # Force HTTPS SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True SECURE_HSTS_SECONDS = 31536000 # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True SECURE_HSTS_PRELOAD = True </code></pre>
<h3>Logging Configuration</h3> <p>Capture errors in a file and optionally forward them to external services.</p> <pre><code># settings.py LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '[%(asctime)s] %(levelname)s %(name)s %(message)s' }, }, 'handlers': { 'file': { 'level': 'ERROR', 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'logs/error.log'), 'formatter': 'verbose', }, }, 'loggers': { 'django': { 'handlers': ['file'], 'level': 'ERROR', 'propagate': True, }, }, } </code></pre>Setting Up Gunicorn and Nginx
bind = '0.0.0.0:8000' workers = multiprocessing.cpu_count() * 2 + 1 loglevel = 'info' accesslog = '-' errorlog = '-' </code></pre>
<h3>Running Gunicorn in Docker</h3> <pre><code># Dockerfile (excerpt) FROM python:3.11-slim WORKDIR /app COPY requirements.txt ./ RUN pip install --no-cache-dir -r requirements.txt COPY . . RUN python manage.py collectstatic --noinput EXPOSE 8000 CMD ["gunicorn", "myproject.wsgi:application", "-c", "gunicorn_config.py"] </code></pre> <h3>Nginx Reverse‑Proxy Configuration</h3> <pre><code># nginx/myproject.conf upstream django { server 127.0.0.1:8000; }server { listen 80; server_name yourdomain.com www.yourdomain.com;
# Redirect HTTP → HTTPS (optional if you terminate SSL elsewhere)
return 301 https://$host$request_uri;
}
server { listen 443 ssl; server_name yourdomain.com www.yourdomain.com;
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location /static/ {
alias /app/static/; # Path from Docker container
}
location /media/ {
alias /app/media/;
}
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://django;
}
} </code></pre>
<h3>Docker‑Compose Example</h3> <p>Combine Django, Gunicorn, and Nginx in a single <code>docker‑compose.yml</code> file for local testing before deploying to a cloud service.</p> <pre><code># docker-compose.yml version: '3.8' services: web: build: . container_name: django_app env_file: .env volumes: - .:/app expose: - "8000" nginx: image: nginx:stable-alpine container_name: nginx_proxy ports: - "80:80" - "443:443" volumes: - ./nginx:/etc/nginx/conf.d - static_volume:/app/static - media_volume:/app/media depends_on: - web volumes: static_volume: media_volume: </code></pre>Deploying with Docker and CI/CD
jobs: build-and-deploy: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Log in to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and push image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ${{ secrets.DOCKER_USERNAME }}/myproject:latest
- name: Deploy to server via SSH
uses: appleboy/ssh-action@v0.1.7
with:
host: ${{ secrets.SERVER_IP }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
docker pull ${{ secrets.DOCKER_USERNAME }}/myproject:latest
docker compose -f /opt/myproject/docker-compose.yml up -d --remove-orphans
</code></pre>
<h3>Benefits of Containerization</h3> <ul> <li><strong>Isolation:</strong> Each component runs in its own container, preventing dependency clashes.</li> <li><strong>Scalability:</strong> Horizontal scaling is as simple as increasing the replica count in the compose file or orchestrator.</li> <li><strong>Portability:</strong> The same image runs locally, on a VPS, or in managed Kubernetes.</li> </ul> <p>When you push a new tag to the repository, the pipeline rebuilds the Docker image, pushes it to a registry, and then pulls the latest version on the production host, ensuring zero‑downtime deployments when combined with a rolling‑update strategy.</p>