As our application grows, ensuring a consistent development environment and simplifying deployment becomes critical. Docker provides containerization, packaging your application and all its dependencies into a single, isolated unit called a container. This chapter will guide you through Dockerizing our FastAPI chat application.

Purpose of this Chapter

By the end of this chapter, you will:

  • Understand the benefits of Docker for development and deployment.
  • Create a Dockerfile to build a Docker image for our application.
  • Use Docker Compose to run the application along with a database (optional, for real DB).
  • Run your FastAPI chat application inside a Docker container.

Concepts Explained: Docker and Dockerfile

Docker is a platform that uses OS-level virtualization to deliver software in packages called containers. Containers are isolated from each other and bundle their own software, libraries, and configuration files; they can communicate with each other through well-defined channels.

Why Docker?

  • Consistency: “It works on my machine” becomes “It works in my container,” ensuring everyone (developers, QA, production) runs the same environment.
  • Isolation: Containers run independently, avoiding conflicts with other applications or system libraries.
  • Portability: Containers can run consistently across any environment that supports Docker (local, cloud, on-premise).
  • Scalability: Easy to scale by running multiple instances of your application container.

A Dockerfile is a script that contains a series of instructions that Docker uses to build an image. An image is a read-only template that contains the application, its dependencies, and configuration. From an image, you can run one or more containers.

Step-by-Step Tasks

1. Create a .dockerignore File

Similar to .gitignore, a .dockerignore file prevents unnecessary files (like virtual environment files, build artifacts, etc.) from being copied into your Docker image, reducing image size and build time.

Create .dockerignore in your realtime-chat-app root directory:

# .dockerignore
.git
.gitignore
.venv
__pycache__
*.pyc
*.sqlite3
*.db # Exclude our chat.db
Pipfile
Pipfile.lock
Dockerfile
docker-compose.yml
# Include any certificate files if you don't want them baked into the image
# and plan to mount them or manage separately in production
key.pem
cert.pem

2. Create a requirements.txt File

While pipenv is great for development, Docker images often prefer a static requirements.txt for installing dependencies.

From your pipenv shell (or without it, if pipenv is globally available), generate this file:

pipenv lock -r > requirements.txt

This command generates a requirements.txt file with all your project’s direct and transitive dependencies locked to specific versions from Pipfile.lock.

3. Create a Dockerfile

Create Dockerfile in your realtime-chat-app root directory:

# Dockerfile

# Use an official Python runtime as a parent image
FROM python:3.13-slim-bullseye

# Set the working directory in the container
WORKDIR /app

# Install system dependencies if any are needed for our Python packages
# (e.g., build essentials for some compiled libraries)
# For passlib[bcrypt], we generally don't need special system deps on slim images.
# If you run into issues with cryptography or other compiled libraries,
# you might need 'build-essential' and 'libffi-dev' etc.
# RUN apt-get update && apt-get install -y build-essential libffi-dev && rm -rf /var/lib/apt/lists/*

# Copy requirements.txt and install Python dependencies
# This step is cached, so if requirements.txt doesn't change,
# pip install won't run again, speeding up builds.
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy the application code into the container
COPY ./app /app/app
# Copy certificates into the container (for local testing with WSS)
# In production, you might mount these or use a reverse proxy to handle SSL.
COPY key.pem cert.pem /app/

# Expose the port our application will run on (HTTPS/WSS port)
EXPOSE 8443

# Command to run the application
# We use gunicorn for production deployment with multiple workers
# and uvicorn as the worker class.
# For local testing, we can keep the simple uvicorn command for now.
# CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8443", "--ssl-keyfile", "key.pem", "--ssl-certfile", "cert.pem"]

# For development, you might want to enable reload, but not in a production Dockerfile
# To run with reload in Docker (for dev-only container):
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8443", "--ssl-keyfile", "key.pem", "--ssl-certfile", "cert.pem", "--reload"]

# IMPORTANT: In a production setup, you would use Gunicorn with Uvicorn workers
# and potentially manage SSL with a reverse proxy like Nginx or Caddy.
# A production CMD might look like this:
# CMD ["gunicorn", "app.main:app", "--workers", "4", "--worker-class", "uvicorn.workers.UvicornWorker", "--bind", "0.0.0.0:8000"]
# and SSL would be handled by a proxy. We'll stick to uvicorn with SSL directly for simplicity here.

Dockerfile Explanation:

  • FROM python:3.13-slim-bullseye: Starts with a slim Python image, reducing image size.
  • WORKDIR /app: Sets the current directory inside the container.
  • COPY requirements.txt .: Copies the dependency list.
  • RUN pip install ...: Installs dependencies. --no-cache-dir keeps image size down.
  • COPY ./app /app/app: Copies our application code.
  • COPY key.pem cert.pem /app/: Copies our SSL certificates.
  • EXPOSE 8443: Informs Docker that the container listens on port 8443.
  • CMD [...]: The default command to execute when a container starts from this image. Here, we run Uvicorn. 0.0.0.0 makes the server accessible from outside the container. reload is okay for dev containers.

4. Build the Docker Image

Navigate to your realtime-chat-app directory in the terminal and build the image:

docker build -t realtime-chat-app:latest .
  • docker build: Command to build an image.
  • -t realtime-chat-app:latest: Tags the image with a name (realtime-chat-app) and a version (latest).
  • .: Specifies the build context (current directory, where the Dockerfile is).

This might take a few minutes for the first build. Subsequent builds will be faster due to Docker’s layer caching.

5. Run the Docker Container

Once the image is built, you can run a container from it:

docker run -d -p 8443:8443 --name chat-server realtime-chat-app:latest
  • docker run: Command to run a container.
  • -d: Runs the container in detached mode (in the background).
  • -p 8443:8443: Maps port 8443 on your host machine to port 8443 inside the container. This allows you to access the application via https://localhost:8443.
  • --name chat-server: Assigns a readable name to your container.
  • realtime-chat-app:latest: Specifies the image to use.

6. Verify and Test the Containerized Application

  1. Check if the container is running:

    docker ps
    

    You should see chat-server listed with port 0.0.0.0:8443->8443/tcp.

  2. View container logs:

    docker logs chat-server
    

    You should see Uvicorn startup logs.

  3. Test in browser: Open client.html (making sure it’s pointing to wss://localhost:8443). Bypass browser SSL warnings. Register/login and try to chat. It should work just as before, but now running inside a Docker container!

7. Stop and Remove the Container

When you’re done, stop and remove the container:

docker stop chat-server
docker rm chat-server

Tips/Challenges/Errors

  • “Error: Cannot connect to the Docker daemon”: Ensure Docker Desktop (Windows/macOS) or Docker Engine (Linux) is running.
  • “No such file or directory: ‘requirements.txt’”: Ensure pipenv lock -r > requirements.txt was run successfully and requirements.txt is in the same directory as your Dockerfile.
  • SSL certificates: If you run into issues, ensure key.pem and cert.pem are copied correctly and Uvicorn’s command specifies their paths within the container (which is /app/key.pem and /app/cert.pem due to WORKDIR /app and COPY commands).
  • Database persistence (SQLite): If you delete and recreate your Docker container, the chat.db file (which is inside the container’s /app directory) will be lost. To persist data, you would use Docker Volumes or external databases. For now, understand that data is ephemeral.

Summary/Key Takeaways

You’ve successfully Dockerized your FastAPI chat application! You now have a Dockerfile that allows you to build a consistent, portable image, and you can run your application in an isolated container. This is a massive step towards production readiness, as Docker simplifies environment management and deployment.

In the final chapter, we will discuss various deployment strategies and considerations for moving your containerized application into a real production environment.