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
Dockerfileto 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-dirkeeps 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.0makes the server accessible from outside the container.reloadis 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 theDockerfileis).
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 port8443on your host machine to port8443inside the container. This allows you to access the application viahttps://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
Check if the container is running:
docker psYou should see
chat-serverlisted with port0.0.0.0:8443->8443/tcp.View container logs:
docker logs chat-serverYou should see Uvicorn startup logs.
Test in browser: Open
client.html(making sure it’s pointing towss://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.txtwas run successfully andrequirements.txtis in the same directory as yourDockerfile. - SSL certificates: If you run into issues, ensure
key.pemandcert.pemare copied correctly and Uvicorn’s command specifies their paths within the container (which is/app/key.pemand/app/cert.pemdue toWORKDIR /appandCOPYcommands). - Database persistence (SQLite): If you delete and recreate your Docker container, the
chat.dbfile (which is inside the container’s/appdirectory) 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.