Python course Β· Module 6: Async and FastAPI

Production Deployment - Docker & Cloud

5 min read
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.txt

FROM 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      - db

build: . 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 -d

In 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:

  1. Push to GitHub
  2. Connect repo on Render
  3. Add environment variables
  4. Auto-deploy!

Railway:

  1. railway init
  2. railway up
  3. 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. 1. Why is Docker important for FastAPI applications?

  2. 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!

Useful articles