Skip to content

Building Spokane Tech: Part 8

Welcome to part 8 of the "Building Spokane Tech" series! In this article, we'll discuss adding Docker and Docker Compose for running components of our service in containers.

Containerization has become an essential tool for modern web development, and Docker is at the forefront of this revolution. When developing a Django-based web application like ours, using Docker ensures consistency across development and deployed environments. By leveraging Docker Compose, we can efficiently manage multiple services required by our application.

See the live site at: https://www.spokanetech.org

See the latest code on: github

Docker Compose

Docker Compose is a tool that allows you to define and manage multi-container Docker applications using a simple YAML file (docker-compose.yaml). It enables developers to run interconnected services, such as a web application, database, and message broker, with a single command. The Docker Compose basic concepts include:

Key Docker Compose Configuration Options

  • version: Defines the Compose file format version. In our case, we use "3.9", which is one of the latest stable versions.

  • services: Lists all the containers that make up the application. Each service runs in its own container.

Service Configuration Keys

  • image: Specifies the Docker image to use for the container. If the image is not found locally, Docker will pull it from a registry like Docker Hub.

  • build: Defines how to build the image from a Dockerfile. It usually includes:

    • context: The directory containing the Dockerfile.
    • dockerfile: The path to the specific Dockerfile used to build the image.
  • container_name: Gives a custom name to the container instead of a randomly generated one.

  • command: Overrides the default command specified in the Dockerfile, allowing you to run specific commands when the container starts.

  • env_file: Loads environment variables from an external .env file.

  • ports: Maps ports between the container and the host.

  • depends_on: Specifies service dependencies. A container will not start until its dependencies are up and running.

Volumes

Volumes store persistent data outside the container filesystem, ensuring data is not lost when containers are restarted or removed.

Our Services

Let's review the components in our system, each of these will be a service in our docker-compose.yaml file.

  • Django (Web Application) – The core application running on Gunicorn or the Django development server
  • PostgreSQL (Database) – Stores application data
  • Redis (Message Broker) – Used by Celery for task queuing
  • Celery Worker – Executes asynchronous tasks
  • Celery Beat – Handles scheduled tasks
  • Celery Flower – Provides a web UI for monitoring Celery tasks

Our docker-compose.yaml file

version: '3.9'

services:
  django:
    image: spokanetech-django:latest
    container_name: django
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: ./entrypoint.sh
    ports:
      - "8080:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:17
    container_name: postgres_db
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    env_file:
      - .env.compose

  redis:
    image: redis:7.2-alpine
    container_name: redis
    restart: unless-stopped
    ports:
      - "6379:6379"

  worker:
    image: spokanetech-django:latest
    container_name: worker
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: celery -A core worker -l info
    depends_on:
      - redis
      - db

  beat:
    image: spokanetech-django:latest
    container_name: beat
    env_file:
      - .env.compose
    build:
      context: ../..
      dockerfile: src/docker/Dockerfile
    command: celery -A core beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler
    depends_on:
      - redis
      - db

  flower:
    image: spokanetech-django:latest
    container_name: flower
    env_file:
      - .env.compose
    command: ["celery", "-A", "core", "--config=flowerconfig.py", "flower"]
    ports:
      - "5555:5555"
    depends_on:
      - redis
      - db

volumes:
  postgres_data:
  static_volume:

Running the Application

Docker Compose provides several commands to manage services. Here are the basics:

Building containers

To build the containers run: docker-compose build

This builds images for the services defined in docker-compose.yaml using the specified Dockerfile. If an image already exists, it will only rebuild if changes are detected.

Starting containers

To start the containers run: docker-compose up

This starts all services defined in docker-compose.yaml. It also automatically builds missing images if they are not found.

To run the containers in detached mode use: docker-compose up -d

This runs containers in the background and allows applications to run persistently.

Stopping containers

To stop the containers use: docker-compose down

This stops and removes all containers, networks, and volumes (if specified); it does not remove built images.

Rebuild and restart containers

To build the container when running, use: docker-compose up --build

This rebuilds images before starting containers and ensures the latest changes in the Dockerfile are applied.

Accessing Services

All of our components are available on localhost on various their applicable ports:

  • Django App: http://localhost:8080
  • Celery Flower UI: http://localhost:5555
  • PostgreSQL: Connect via localhost:5432
  • Redis: Available on localhost:6379