Hello! Welcome to the next lesson in our "Testing and Production Deployment" module.
In the last lesson, you did a fantastic job learning how to containerize our FastAPI application using a multi-stage Dockerfile. You now have a lean, secure, and portable image of our app. That's a huge step towards professional deployment!
However, a real-world application rarely consists of a single service. It usually communicates with a database, a cache, or other microservices. Running each of these as a separate Docker container with long docker run commands quickly becomes tedious and error-prone. This lesson tackles that exact problem by focusing on our learning outcome: Create a docker-compose.yml file to orchestrate the application and its database for local development.
We will use Docker Compose, a powerful tool for defining and managing multi-container applications. With a single configuration file, you'll be able to spin up your entire local development environment—FastAPI app and PostgreSQL database included—with just one command.
For someone with your background in Django, you can think of this as a supercharged version of managing your local development setup. Instead of manually starting the Django runserver, ensuring your local PostgreSQL service is running, and maybe firing up a Redis server, Docker Compose handles all of that orchestration for you in a clean, reproducible way.
What is Docker Compose?
At its heart, Docker Compose is a tool that reads a YAML file (by default, docker-compose.yml) to configure and run your application's services. This file acts as the blueprint for your entire application stack.
Let's watch a brief introduction from the official Docker team to see why this is so useful.
How to Create a Great Local Python Development Environment with Docker
This video from the Docker channel gives a concise overview of how Docker Compose simplifies managing multi-container applications by replacing long, complex Docker commands with a single YAML file.
Watch from 17:30 to 18:56. The key takeaway is understanding that Docker Compose is a tool for defining and running a multi-container setup from a single configuration file.
The Anatomy of a docker-compose.yml File
A Docker Compose file is structured around a few key top-level elements. For our purposes, the most important are:
services: This is the main block where you define each container that makes up your application stack (e.g., your FastAPIwebapp, adbfor the database).volumes: This section is used to declare named volumes, which are the preferred mechanism for persisting data generated by and used by Docker containers, like your database files.networks: While often handled automatically, this allows you to define custom networks for your services to communicate through.
To get a better feel for the structure, let's see how the file is created.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
This clip from a tutorial by Ssali Jonathan shows the initial creation of the docker-compose.yml file and introduces the services block where we'll define our application and database containers.
Watch from 10:10 to 11:31. Focus on the YAML syntax and how each service (like app and db) will be a separate entry under the services key.
Now, let's define our two services one by one.
1. The FastAPI Application Service
First, we'll define the service for our FastAPI application, which we'll call web. This service will be built using the Dockerfile you created in the previous lesson.

Here are the key instructions we'll use:
build: .: This tells Docker Compose to build an image from theDockerfilelocated in the current directory (the project root).ports: This maps a port on your local machine (the host) to a port inside the container. For example,- "8000:8000"exposes the container's port 8000 on your machine's port 8000.volumes: For local development, we want to see our code changes reflected immediately without rebuilding the image. We achieve this by mounting our local source code directory into the container. For example,- ./app:/app/appmounts the local./appfolder to the/app/appfolder inside the container. This, combined with Uvicorn's--reloadflag, gives you live reloading.
Let's watch this in action.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
The following segment shows how to configure the application service. Pay close attention to the build context and the ports mapping.
Watch from 11:31 to 12:55. The video uses build: . and defines the port mapping. We will add a volume for live-reloading shortly.
2. The PostgreSQL Database Service
Next, we'll define the db service. Instead of building it from a Dockerfile, we'll use a pre-built image directly from Docker Hub.
Key instructions:
image: postgres:15-alpine: Pulls the official PostgreSQL version 15 image, using the lightweightalpinevariant.volumes: This is critical. To ensure our database data isn't lost every time we stop the container, we must persist it. We'll map a named volume to the directory inside the container where PostgreSQL stores its data (/var/lib/postgresql/data/).environment: The official PostgreSQL image is configured using environment variables. We'll use this to set the username, password, and database name.expose: Unlikeports,exposemakes a port accessible to other services on the same Docker network, but not to the host machine. This is a good security practice, as you typically don't need to connect to the database directly from your local machine, only from your app container.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
This is the most important part of setting up our database. This clip explains how to define the database service, including setting the image, configuring environment variables, and, most importantly, creating a volume for data persistence.
Watch from 14:03 to 18:38. Focus on three things: Using image: postgres:16 (you can use 15 or 16). Setting POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB. Understanding how the volumes section for the service (db-data:/var/lib/postgresql/data) links to the top-level volumes declaration (db-data:).
Putting It All Together
We've seen the individual pieces. Now, let's study a complete, well-commented docker-compose.local.yml file that brings everything together, including the connection between the services.
The Ultimate FastAPI Tutorial Part 13 - Using Docker to Deploy
This article from christophergs.com provides an excellent, detailed walkthrough of a docker-compose.local.yml file for a FastAPI and PostgreSQL setup. It's a perfect text-based reference that cements the concepts we just saw in the video.
Read the section titled 'PostgreSQL Local Development With The FastAPI Web Framework'. Study the docker-compose.local.yml file and the numbered breakdown that follows it. Pay special attention to: Point 4: How the environment variables in the db service are set. Point 8: Using volumes to mount the app source code for live reloading. Point 9: Setting the DATABASE_URL environment variable for the web service. Notice how the hostname is db—the name of our database service.
Best Practices: Using a .env File
Hardcoding credentials like your database password directly in docker-compose.yml is not a good practice, even for local development. A better approach is to store them in a .env file in your project root. Docker Compose automatically reads this file and makes the variables available.
You can then reference these variables in your docker-compose.yml file using the ${VARIABLE_NAME} syntax.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
Let's see how to move our database credentials out of the compose file and into a separate .env file. This is a simple but important step towards a more professional workflow.
Watch from 20:49 to 22:19. Observe how the hardcoded values for the PostgreSQL environment variables are replaced with ${...} syntax, which pulls the values from the newly created .env file.
Running and Managing Your Application Stack
With your docker-compose.yml and .env files in place, managing your entire stack becomes incredibly simple.
First, let's see how to start everything up.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
With our file complete, this clip shows the magic of docker compose up. It builds the app image, pulls the database image, creates a network, and starts both services.
Watch from 18:38 to 20:06. Notice how a single command orchestrates the entire multi-container startup process.
Here are the essential commands you'll use:
docker compose up: Builds images if they don't exist and starts all services defined in yourdocker-compose.yml. It attaches to the logs of all containers.docker compose up -d: Starts the services in "detached" mode, running them in the background.docker compose down: Stops and removes the containers, networks, and volumes created byup. Usedocker compose down -vto also remove named volumes (use with caution!).docker compose logs -f <service_name>: Follows the logs for a specific service (e.g.,webordb).docker compose exec <service_name> <command>: Executes a command inside a running container. This is very useful for debugging or running management commands. For example:docker compose exec db psql -U your_user -d your_db.
Modern Live Reloading with watch
While mounting source code with a volume works great for live reloading, Docker Compose has recently introduced a more integrated watch mode specifically for development. It can be configured to automatically sync file changes and even rebuild the service if a critical file like pyproject.toml changes.
This is a modern feature you requested to learn about, and it streamlines the development workflow even further.
Setting Up a Dev Environment for FastAPI, PostgreSQL, SQLAlchemy & Alembic with Docker
Finally, let's look at a newer, more powerful way to handle live reloading in development: docker compose watch. This feature provides more granular control over how file changes are handled.
Watch from 42:33 to 48:30. Understand the develop and watch sections being added to the app service. The key idea is that you can define actions like sync (for code files) and rebuild (for dependency files), giving you a more robust development loop than a simple volume mount.
Conclusion
Excellent work! You have now mastered a cornerstone of modern development workflows. By using Docker Compose, you can define, share, and run a complete multi-service application environment with a single command. This ensures consistency across your team and simplifies the setup process immensely.
Key Takeaways:
- Docker Compose orchestrates multi-container applications using a
docker-compose.ymlfile. - Each container is defined as a
service(e.g.,web,db). - The
buildinstruction creates an image from aDockerfile, while theimageinstruction pulls a pre-built image. - Named volumes are essential for persisting data, especially for stateful services like databases.
- Services on the same Docker Compose network can communicate using their service names as hostnames.
- Using a
.envfile is the standard practice for managing environment variables and secrets. - The
docker compose upanddocker compose downcommands are your primary tools for managing the application stack.
Preview of the Next Lesson:
We've established a robust local development environment. The next logical step is to prepare for a true production environment. In the next lesson, you will learn how to configure and run the application in production using Gunicorn as a process manager for Uvicorn workers. This will address the need for parallel request handling and process management, ensuring your application is performant and resilient.