Python course Β· Module 6: Async and FastAPI
Production Deployment - Docker & Cloud
In this lesson5
Welcome! Darwin here with the last lesson of Module 6 - deploying FastAPI to production!
On your laptop the Safari API runs perfectly. A colleague starts it on theirs and gets an error, because they have a different Python version, and the cloud server does not have PostgreSQL yet. The famous "works on my machine" is the most common problem with a first deployment, and Docker solves it by packing the application together with its whole environment.
Safari analogy: Deployment is like opening the safari park to the public - we must ensure security, scalability, and 24/7 reliability! A container, in turn, is a packed expedition backpack: in every camp you unpack exactly the same gear.
Dockerfile for FastAPI
A Docker image is a ready package with the system, Python, dependencies and code, and a container is a running copy of an image. We write the recipe for an image in a Dockerfile. The first part picks the base and installs the dependencies:
1# Dockerfile
2FROM python:3.11-slim
3
4WORKDIR /app
5
6COPY requirements.txt .
7RUN pip install --no-cache-dir -r requirements.txtFROM names the base image - the slim variant is much smaller than the full one. WORKDIR sets the working directory. Note that we first copy only requirements.txt: Docker remembers every step as a layer, so as long as the dependency list does not change, installing packages on later builds comes from the cache. --no-cache-dir stops pip from leaving the downloaded archives in the image.
The second part copies the code and says how to start the application:
1COPY . .
2
3CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]COPY . . brings in the rest of the project - add a .dockerignore file so that .venv, .git and secrets do not end up there. --host 0.0.0.0 is essential: the default 127.0.0.1 would accept connections only from inside the container. Writing CMD as a JSON list (exec form) means uvicorn receives the stop signal directly and can shut down gracefully.
You build and run the image with two commands:
1docker build -t safari-api .
2docker run -p 8000:8000 safari-api-t gives the image a name, and the dot means the current directory with the Dockerfile. -p 8000:8000 connects your computer's port to the container's port, so the API answers at http://localhost:8000.
docker-compose.yml
The application still needs a database. Docker Compose describes several services in one file that start together. First the API service:
1version: '3.8'
2
3services:
4 api:
5 build: .
6 ports:
7 - "8000:8000"
8 environment:
9 - DATABASE_URL=postgresql+asyncpg://user:password@db/safari
10 depends_on:
11 - dbbuild: . builds the image from our Dockerfile. We pass the database URL through an environment variable, and the host name db is simply the name of the second service - Compose creates a network in which services see each other by name. The version line is obsolete today: the current Compose specification ignores it and prints a warning, so you can leave it out of new files.
Now the database and the volume:
1 db:
2 image: postgres:15
3 environment:
4 - POSTGRES_USER=user
5 - POSTGRES_PASSWORD=password
6 - POSTGRES_DB=safari
7 volumes:
8 - postgres_data:/var/lib/postgresql/data
9
10volumes:
11 postgres_data:The postgres_data volume keeps the database files outside the container, so the data survives its removal. depends_on only sets the start order, it does not wait until PostgreSQL is ready for connections - the application should be able to retry, or you can add a healthcheck.
Run it:
The whole stack starts with one command, and -d runs it in the background:
1docker-compose up -dIn newer Docker installations Compose is a plugin and you type docker compose up -d, with a space instead of a hyphen. I recommend that form, because the older standalone docker-compose program is no longer developed.
Production checklist
- Environment variables (DATABASE_URL, SECRET_KEY)
- HTTPS/TLS certificates
- CORS configuration
- Rate limiting
- Logging & monitoring
- Database migrations (Alembic)
- Backup strategy
- Load balancer
Each item guards against a different problem: secrets in environment variables will not leak from the repository, and Alembic changes the database structure without hand-written SQL. The passwords in the Compose file above are fit for learning only.
Deploy to Render/Railway
PaaS platforms build and run the container for you. All you need is to connect the repository:
Render:
- Push to GitHub
- Connect repo on Render
- Add environment variables
- Auto-deploy!
Railway:
- railway init
- railway up
- Done!
railway init and railway up are Railway command-line commands: the first creates a project, the second uploads the code and deploys it. Check details such as the required port settings in the documentation of the platform you choose.
Module 6 Summary
Congratulations! You have completed Module 6: FastAPI!
You learned:
- Async/await fundamentals
- FastAPI basics and endpoints
- Pydantic validation
- Async databases (SQLAlchemy)
- JWT authentication
- Testing with pytest
- Production deployment (Docker)
The Safari Database API now has everything it needs for a first deployment: validation, a database, authentication, tests and a container. How many requests per second it will handle depends on the server and the database, so always measure it before heavy traffic.
Darwin is proud! Module 7 goes deeper into DevOps: testing, code quality, Docker, CI/CD, the cloud and monitoring.
Remember: a container is a packed expedition backpack - what worked in the training camp will work on every savanna.
Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Why is Docker important for FastAPI applications?
2. What should a Dockerfile for a FastAPI application contain?
These are 2 of 3 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Vertical ordering
Arrange the Dockerfile for a FastAPI application:
- Horizontal ordering
How do you build a Docker image with the tag 'safari-api'?
- Click in order
Arrange the command to run a container on port 8000:
- Code editor
Create a Dockerfile: python:3.11-slim image, WORKDIR /app, copy and install requirements.txt, copy the code, run uvicorn main:app on 0.0.0.0:8000.
- Horizontal ordering
Arrange the elements of an asynchronous function definition:
- Horizontal ordering
Arrange the elements of a FastAPI decorator:
- Horizontal ordering
Arrange the asyncio.gather call:
- Code editor
Create a GET '/species/{species_id}' endpoint with a path parameter.
- Code editor
Create a GET '/species' endpoint that returns a list of species.
- Code editor
Create a Species model with fields name (str) and habitat (str).
- Vertical ordering
Arrange the steps for creating a FastAPI application:
- Code editor
Write an async def fetch_data() that uses await to fetch data.
- Code editor
Build a Safari Async API: species CRUD with an async database, JWT authentication, tests, and a Dockerfile. Use everything you've learned!