Skip to content

Docker Production v2

Aleksandar Dimitrov edited this page Feb 24, 2025 · 12 revisions

This page details the Docker-based Production v2 setup, focusing on its architecture, networking, orchestration, and deployment.

Table of Contents

  1. Overview
  2. Network Diagram
  3. Docker Compose Services
    3.1 The Wall API
    3.2 Redis
    3.3 PostgreSQL
    3.4 Celery Worker 1
    3.5 Celery Worker 2
    3.6 Celery Beat
  4. Dockerfile
    4.1 Base Image
    4.2 Application Image
    4.3 Celery Lightweight Image
  5. Image Lifecycle
    5.1 Image Build and Push to GHCR
    5.2 Pulling Images and Deployment

1. Overview

The prod_v2 setup introduces Docker Swarm for orchestration, enabling secure secrets management and consistent service deployment while maintaining compatibility with future scaling.

  • Although Production v2 runs on a single node, Swarm is used primarily to:

    ✔ Leverage Docker Secrets - Ensures sensitive credentials are securely stored and accessed without being exposed in environment variables.

    ✔ Maintain a scalable architecture - Allows for seamless expansion to multiple nodes if needed, without major reconfiguration.

    ✔ Preserve the Docker Compose workflow - docker-compose-prod-v2.yml is derived from docker-compose-prod-v1.yml, adapting the existing Compose-based structure for Swarm. This ensures a familiar configuration while integrating Swarm’s secrets management and orchestration features.

    ✔ Support existing volume-based persistence - Since all services run on a single node, local Docker volumes remain effective for data persistence.


2 Network Diagram

This diagram represents the established network architecture used across the prod_v1 and demo deployments, which also applies to prod_v2. This setup ensures a stable foundation while supporting enhancements for orchestration and security.

Docker Network Prod v2

View full image

🗒️ For a comparison with existing setups, see the Docker - Production v1 and Demo page.


3. Docker Compose Services

The prod_v2 setup defines its service stack in docker-compose-prod-v2.yml, which is deployed using Docker Swarm.


Service name Image Ports Named Volumes Secrets Used
the_wall_api ghcr.io/alexandar83/
the-wall-api:latest
8000:8000 shared_sim_logs:
/app/logs
✅
redis redis:7.4.1 6379:6379 the_wall_api_redis_data_prod:
/data
✅
postgres postgres:15.3 5432:5432 the_wall_api_postgres_data_prod:
/var/lib/postgresql/data
✅
celery_worker_1 ghcr.io/alexandar83/
the-wall-api:latest
- shared_sim_logs:
/app/logs
✅
celery_worker_2 ghcr.io/alexandar83/
the-wall-api:latest
- shared_sim_logs:
/app/logs
✅
celery_beat ghcr.io/alexandar83/
celery-lightweight:latest
- celery_beat_schedule:
/var/lib/celery/data
✅

🗒️ In Docker Swarm, services do not have static container names. Instead, they follow the format <service_name>.<task_id>.<node_id>, for example:
  •  the_wall_api.1.sj9f8g2h7k3l
  •  celery_worker_1.2.jd83kd9s8g5k

✔ The shared_sim_logs volume enables unified log management, allowing the API and Celery workers to store, update, and access logs in a shared location, just as in previous deployment modes.

Additional Service Details:

3.1 The Wall API

Serves as the primary application service.

  • Starts with the following command:

    python /app/config/scripts/docker/wait_for_postgres.py && \
    python manage.py makemigrations the_wall_api && \
    python manage.py migrate && \
    exec gunicorn --bind 0.0.0.0:8000 config.wsgi
    • Command Breakdown:

      • wait_for_postgres.py: Script to wait for the full initialization of the PostgreSQL service.
      • makemigrations: Detects and prepares database schema changes.
      • migrate: Executes pending database migrations to update the schema before starting the server.
      • gunicorn: Serves as the production WSGI server, handling requests and binding to all interfaces.

      ✔ Docker Swarm does not support the depends_on directive. The wait_for_postgres.py script ensures PostgreSQL is fully ready before applying migrations.

      ✔ gunicorn enhances request handling efficiency in production, ensuring optimal performance under load.

  • Uses the_wall_api_prod.env to load environment variables.

  • Sets PROJECT_MODE=prod_v2 to indicate the production version of the environment.

  • Mounts the Redis password, PostgreSQL credentials, and the app's secret key in /run/secrets/.

    ✔ The secrets are read by the application at runtime, ensuring they are never stored in environment variables or image layers. This enhances security by keeping sensitive data outside the container’s build process and preventing accidental exposure.

3.2 Redis

Handles data caching and functions as a message broker for Celery task queues.

  • Uses a Docker-managed volume (redis_data_prod) to persist data across restarts.
  • Mounts config/envs/common/redis.conf to /usr/local/etc/redis/redis.conf inside the container.
  • The start command reads the dedicated password from the Docker secret at runtime:
    redis-server /usr/local/etc/redis/redis.conf --requirepass "$$(cat /run/secrets/redis_password)"

✔ Mounting redis.conf enables dynamic configuration, allowing environment-specific adjustments without modifying the base image or requiring container rebuilds.

💡 For a detailed breakdown of redis.conf settings, refer to the Redis configuration file section.

3.3 PostgreSQL

Manages persistent database storage for the API.

  • Utilizes a Docker-managed volume (postgres_data_prod) to retain data persistently across restarts.
  • Credentials are read from Docker Secrets, which store:

✔ Docker Secrets keep credentials separate from environment variables and image layers, enhancing security and preventing accidental exposure.

3.4 Celery Worker 1

Processes sequential Celery tasks.

  • Starts using the start_celery_computation_worker.sh script, which:
    • Runs the Celery worker without a --concurrency flag, allowing automatic adjustment based on system resources.
      • If CONCURRENT_SIMULATION_MODE is set to multiprocessing_v1,2, or 3, applies explicit concurrency settings (refer to script for details).
    • Assigns the worker to the concurrent_tasks queue.

  • Configurations identical to The Wall API:

3.5 Celery Worker 2

Processes sequential Celery tasks.

  • Worker 2 follows the same configuration structure as Celery Worker 1, differing only in its start command:
    celery -A config worker --loglevel=info --queues=sequential_tasks --concurrency=1
    • Command Breakdown:
      • celery -A config worker: Launches a Celery worker, using the Celery app instance from config/celery.py.
      • --loglevel=info: Sets the worker’s logging level to info.
      • --queues=sequential_tasks: Assigns the worker to the sequential_tasks queue.
      • --concurrency=1: Enforces single-task execution to maintain sequential processing.

3.6 Celery Beat

Schedules recurring Celery tasks, including database cleanup, file archiving, and archive retention.

  • Uses a lightweight image based on Dockerfile.prod.
  • Start command:
    python /app/config/scripts/docker/wait_for_celery_worker.py && \
    celery -A config beat --loglevel=info -s /var/lib/celery/data/celerybeat-schedule
    • wait_for_celery_worker.py: Waits for both Celery workers to be ready.
      ✔ Docker Swarm does not provide built-in service dependencies. The script ensures both Celery workers are running before starting Celery Beat.
    • -A config beat: Starts Celery Beat for task scheduling.
    • -s /var/lib/celery/data/celerybeat-schedule: Defines the schedule file’s location within the container.
  • Persists the task schedule in the celery_beat_schedule volume, preventing loss across restarts.
  • Applies a lightweight Celery configuration (LIGHT_CELERY_CONFIG=True), reducing overhead by skipping Django initialization.

  • Configurations identical to Celery Worker 1:

✔ The lightweight image minimizes overhead by excluding unnecessary dependencies.


4. Dockerfile

Dockerfile.prod defines a multi-stage build used in Docker Swarm
to create images for the API, Celery Worker 1, Celery Worker 2, and Celery Beat.

4.1 Base Image

Establishes the base environment for subsequent build stages.

  • Configures essential environment variables:

    • PYTHONDONTWRITEBYTECODE=1 (prevents .pyc file generation).
    • PYTHONUNBUFFERED=1 (ensures real-time logging output without buffering).
    • PYTHON_VERSION=3.11.9 (acts as a reference for scripts and automation but does not impact the installed version).
    • LOGS_DIR_NAME=logs (standardizes the log folder name across all build stages, ensuring consistency for the application and Celery workers).
  • Sets /app as the working directory.

  • Creates a non-root user for security:

    RUN adduser -u 5678 --disabled-password --gecos "" appuser
    • Disables password authentication to skip interactive logins.
    • Leaves user details empty to avoid prompts.
    • Assigns the username 'appuser'.

✔ Using a minimal slim-bullseye base image optimizes container size and reduces security risks.

✔ Running as a non-root user enhances security by restricting elevated privileges within the container.

4.2 Application Image

Serves as the base for Celery Worker 1, Celery Worker 2, and the API container, ensuring the required application code is available for each service.

  • Installs system dependencies gcc and libpq-dev (needed to compile the psycopg2 PostgreSQL adapter from source).

  • Copies requirements.txt and installs Python dependencies with pip.

  • Installs gunicorn separately to serve the application as a WSGI server.
    🗒️ gunicorn is not included in requirements.txt since it is not an application dependency.

  • Copies the application to /app inside the container.
    🗒️ .dockerignore filters copied files to reduce image size and optimize the build.

  • Configures log folder permissions:

    • Creates /app/${LOGS_DIR_NAME} to avoid permission issues for Celery workers.
    • Assigns ownership to the non-root user (appuser).
  • Runs all subsequent processes as appuser.


✔ Reduces image size by:
  • Removing package manager caches after installing PostgreSQL dependencies (rm -rf /var/lib/apt/lists/)
  • Disabling package caching during Python dependency installation (--no-cache-dir)

4.3 Celery Lightweight Image

Used by the Celery Beat service.

  • Installs only the necessary Celery and Redis dependencies from requirements.txt using:

    RUN grep -E '^celery==|^redis==' /app/requirements.txt > filtered-requirements.txt  
    RUN pip install --no-cache-dir -r filtered-requirements.txt  

    ✔ The extraction command ensures that the exact versions frozen in requirements.txt are used, maintaining consistency across deployments.

  • Copies the application to /app.
    🗒️ File selection is controlled with .dockerignore.

  • Configures Celery Beat schedule and log storage:

    • Creates /var/lib/celery/data to persist the task schedule.
    • Sets up the logs folder and assigns ownership to appuser.

✔ The lightweight design reduces the package footprint, making the container smaller and improving build speed.


5. Image Lifecycle

The prod_v2 environment utilizes a structured workflow for building, pushing, and deploying Docker images. A full application image runs the API and Celery workers, while a lightweight Celery image is streamlined for Celery Beat. Both images are versioned and stored in GHCR for centralized management and deployment.

📝 All commands in this section should be run from the application's root folder.

5.1 Image Build and Push to GHCR

Full Image
docker build --no-cache \
--target the_wall_api_image \
-t ghcr.io/alexandar83/the-wall-api:v2 \
-t ghcr.io/alexandar83/the-wall-api:latest \
-f ./config/docker/Dockerfile.prod \
.

docker push ghcr.io/alexandar83/the-wall-api --all-tags
  • Commands Breakdown
    • --no-cache: Forces a fresh build by skipping cached layers.
    • --target the_wall_api_image: Defines the multi-stage build target for the API and Celery workers image.
    • -t ghcr.io/alexandar83/the-wall-api:v2 -t ghcr.io/alexandar83/the-wall-api:latest: Assigns two tags to the built image: v2 and latest.
    • -f ./config/docker/Dockerfile.prod: Specifies the Dockerfile location for the build.
    • .: Sets the current directory as the build context.
    • docker push ghcr.io/alexandar83/the-wall-api --all-tags: Pushes all image tags to GHCR.

Lightweight Image
docker build --no-cache \
--target celery_lightweight_image \
-t ghcr.io/alexandar83/celery-lightweight:v2 \
-t ghcr.io/alexandar83/celery-lightweight:latest \
-f ./config/docker/Dockerfile.prod \
.

docker push ghcr.io/alexandar83/celery-lightweight --all-tags

5.2 Pulling Images and Deployment

docker stack deploy -c config/docker/docker-compose-prod-v2.yml prod_stack --detach=true
  • Command Breakdown:

    • docker stack deploy – Deploys a new stack in Docker Swarm.
    • -c config/docker/docker-compose-prod-v2.yml – Specifies the path to the Compose file used for deployment.
    • prod_stack – Defines the stack name, grouping all services under this identifier.
    • --detach=true – Runs the deployment in the background, allowing the console shell in which the command is executed to be used for other tasks.



💡 For detailed environment installation instructions, see the Production v2 setup guides for Windows and Linux (WSL2).


Next:   DRF Spectacular
Back to: Docker - Production v1 and Demo

Return to Top
DTS Index
Home Page


Clone this wiki locally