Building a Production-Ready Local Development Environment with Docker Compose
“It works on my machine.” It’s a phrase every developer has likely experienced or said at least once. Subtle differences between the development environment and the production environment where the actual service runs are a major cause of unexpected bugs and deployment failures. This is due to numerous variables like library versions, operating systems, and system configurations. The technology that emerged to solve these problems is Docker.
Docker packages an application and all its dependencies into an isolated space called a ‘container,’ ensuring it runs identically in any environment. This allows developers to easily set up an environment on their local PC that is nearly identical to production and share a consistent development environment with team members. In this post, we will provide a practical, step-by-step guide to building a multi-container development environment using Docker Compose, consisting of the Python Django web framework, a PostgreSQL database, and a Redis cache server.
![]()
© AI Generated by Imagen 4.0
Why Should You Use Docker?
Before we dive into the setup, let’s clarify why you should use Docker.
- Environmental Consistency: Docker bundles everything needed to run an application (code, runtime, system tools, libraries, etc.) into a single container image. This image guarantees an identical environment across development, staging, and production, fundamentally preventing bugs caused by ‘environmental differences’.
- Isolated Environments: Each container runs in isolation from the host system and other containers. This prevents conflicts that can arise when different projects require different versions of libraries or databases.
- Rapid Setup and Deployment: You can automate development environment setup using code-based configuration files like
Dockerfileanddocker-compose.yml. When a new team member joins, they can set up the entire development environment instantly with just a few commands, without a complex installation process. - Cloud-Friendly: Modern cloud services like AWS ECS (Elastic Container Service) and EKS (Elastic Kubernetes Service) are all container-based. Using Docker locally is the first step toward a cloud deployment pipeline.
Designing the Project Structure
For efficient management, we’ll start with the following directory structure.
my-project/
├── .env # Environment variable file
├── docker-compose.yml # Docker Compose configuration file
└── app/
├── Dockerfile # Dockerfile for the Django app
├── manage.py
├── myproject/
│ ├── settings.py
│ └── ...
└── requirements.txt # Python dependency list
Step 1: Create the Dockerfile for the Django Application
First, let’s create the Dockerfile that defines the container image for running our Django application. This file will be located in the app/ directory.
app/Dockerfile
# 1. Select the base image
FROM python:3.11-slim
# 2. Set Python-related environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# 3. Create and set the working directory
WORKDIR /app
# 4. Copy and install dependencies
COPY requirements.txt .
RUN pip install --upgrade pip && pip install -r requirements.txt
# 5. Copy the application code
COPY . .
# 6. Run the Django development server
CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]
app/requirements.txt
Django>=4.2
psycopg2-binary
redis
gunicorn
The Dockerfile above is based on the Python 3.11 image. It defines the process of installing the necessary packages, copying the application code, and then running the development server.
Step 2: Orchestrate Services with Docker Compose
Now, we will create a docker-compose.yml file in the project’s root directory to define and connect our multiple services (web, database, cache) at once.
docker-compose.yml
version: '3.8'
services:
# 1. Django web service
web:
build:
context: ./app
dockerfile: Dockerfile
command: python manage.py runserver 0.0.0.0:8000
volumes:
- ./app:/app # Sync local code with container code in real-time
ports:
- "8000:8000" # Map host port 8000 to container port 8000
env_file:
- ./.env # Load environment variables from file
depends_on:
- db
- redis
# 2. PostgreSQL database service
db:
image: postgres:15
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_DB=${SQL_DATABASE}
- POSTGRES_USER=${SQL_USER}
- POSTGRES_PASSWORD=${SQL_PASSWORD}
# 3. Redis cache service
redis:
image: redis:7
volumes:
postgres_data: # Volume to persist database data
This configuration file defines three services: web, db, and redis.
web: This service is built from theapp/Dockerfile. It mounts the localappdirectory to/appinside the container, ensuring that code changes are reflected immediately.db: This service uses the official PostgreSQL image. It uses a volume namedpostgres_datato persist data even if the container is removed.redis: This service uses the official Redis image.depends_on: This sets a dependency, ensuring thewebservice starts only after thedbandredisservices are running.
Step 3: Manage Environment Variables (the .env file)
For sensitive information like database credentials, it is safer and better practice to use environment variables instead of hardcoding them. Create a .env file in the project root.
.env
# PostgreSQL Settings
SQL_DATABASE=mydb
SQL_USER=myuser
SQL_PASSWORD=mypassword
# Django Settings
SECRET_KEY=your-django-secret-key-here
DEBUG=1
These separated environment variables are injected into the web service via the env_file directive in docker-compose.yml. You will need to modify Django’s settings.py to read these values using os.environ.get().
Running and Verifying
All the configurations are complete. Now, run the following commands in your terminal to start the entire development environment.
# Build and run the container images (add the -d flag to run in the background)
docker-compose up --build
# Check the list of running containers
docker-compose ps
# Run Django database migrations
docker-compose exec web python manage.py migrate
Once the docker-compose up command runs successfully, you can open your web browser and navigate to http://localhost:8000 to verify that the Django application is running correctly and connected to the PostgreSQL database and Redis. After modifying your code, the changes will be reflected in the container immediately without needing a separate build step.
As you can see, Docker Compose allows you to easily manage complex multi-service architectures with just a few configuration files and commands. This is a powerful tool not only for personal projects but also for team projects with multiple developers, as it standardizes the development environment and maximizes productivity. Furthermore, a containerized application like this gives you a significant advantage when migrating to a cloud environment like AWS in the future.
References
- Docker Documentation
- Docker Compose File Reference
- The Web framework for perfectionists with deadlines
- Amazon Elastic Container Service (ECS)