I Dockerized My First FastAPI Application — From Python Code to Container
In my previous article, I explained the Docker concepts that confused me when I first started learning:
- Images
- Containers
- Ports
- Volumes
- Dockerfiles
But understanding Docker is one thing.
Actually putting an application inside a container is another.
So this time, I decided to take a small FastAPI application and Dockerize it from scratch.
Here's exactly what I did.
What We're Building
The application is intentionally simple.
It's a small FastAPI API with a health-check endpoint.
Our final architecture will look like this:
FastAPI Application
│
↓
Dockerfile
│
docker build
│
↓
Docker Image
│
docker run
│
↓
Docker Container
│
Port 8000
│
↓
http://localhost:8000
Nothing complicated.
The goal is to understand the workflow.
Step 1 — Create the FastAPI Application
First, I created a simple Python project.
The structure looks like this:
fastapi-docker/
│
├── app.py
├── requirements.txt
└── Dockerfile
My app.py contains:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {
"message": "Hello from FastAPI running inside Docker!"
}
@app.get("/health")
def health():
return {
"status": "healthy"
}
This gives us two endpoints:
GET /
GET /health
Step 2 — Add the Dependencies
Next, I created requirements.txt.
fastapi
uvicorn[standard]
These are the two main packages we need.
FastAPI provides the web framework.
Uvicorn runs the application.
Step 3 — Test It Locally
Before introducing Docker, I wanted to make sure the application itself worked.
I installed the dependencies:
pip install -r requirements.txt
Then started the application:
uvicorn app:app --reload
The API was available at:
http://localhost:8000
And the interactive API documentation was available at:
http://localhost:8000/docs
This is an important step.
If the application doesn't work before Docker, putting it inside a container won't magically fix it.
Step 4 — Create the Dockerfile
Now comes the interesting part.
I created a file called:
Dockerfile
with:
FROM python:3.12
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
Let's break this down.
FROM
FROM python:3.12
This tells Docker to start with the official Python 3.12 image.
Instead of manually installing Python inside our container, we use an existing base image.
WORKDIR
WORKDIR /app
This creates and switches to /app inside the container.
Our application will live there.
COPY requirements.txt
COPY requirements.txt .
We copy the dependency file into the container first.
RUN pip install
RUN pip install --no-cache-dir -r requirements.txt
This installs our Python dependencies while the image is being built.
COPY the Application
COPY . .
Now we copy the rest of our project into the container.
EXPOSE
EXPOSE 8000
This documents that our application uses port 8000.
One important detail:
EXPOSE does not publish the port to our computer.
We'll do that when we run the container.
CMD
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]
This is the command that starts our FastAPI application when the container runs.
The important part here is:
--host 0.0.0.0
The application needs to listen on all interfaces inside the container so that Docker can forward traffic to it.
Step 5 — Build the Docker Image
Now we're ready to build the image.
From the project directory:
docker build -t fastapi-docker .
Let's break that command down:
docker build
Build an image.
-t fastapi-docker
Give the image a name.
.
Use the current directory as the build context.
After the build completes, I can check my images:
docker images
I should see something similar to:
REPOSITORY TAG IMAGE ID
fastapi-docker latest ...
We now have our own Docker image.
Step 6 — Run the Container
Now we can create a container from our image.
docker run -d -p 8000:8000 --name fastapi-app fastapi-docker
Here's what each part means:
-d
Run the container in the background.
-p 8000:8000
Map:
Host port Container port
8000 → 8000
--name fastapi-app
Give the container a name.
fastapi-docker
Use our Docker image.
Step 7 — Check the Container
Let's make sure it's running:
docker ps
We should see something similar to:
CONTAINER ID IMAGE STATUS PORTS
abc123 fastapi-docker Up 10 seconds 0.0.0.0:8000->8000/tcp
This is the point where the whole Docker concept starts to become real.
We have:
Python Code
↓
FastAPI
↓
Dockerfile
↓
Docker Image
↓
Docker Container
↓
Port 8000
↓
Browser
Step 8 — Test the API
Now open:
http://localhost:8000
We should get:
{
"message": "Hello from FastAPI running inside Docker!"
}
We can also test:
http://localhost:8000/health
which returns:
{
"status": "healthy"
}
And one of my favorite FastAPI features:
http://localhost:8000/docs
opens the interactive Swagger UI.
Now we're running an API inside a Docker container.
Step 9 — Look at the Container Logs
If something goes wrong, logs are one of the first places to look.
docker logs fastapi-app
This shows the output generated by the application.
You can also follow the logs:
docker logs -f fastapi-app
This is particularly useful when debugging applications running inside containers.
Step 10 — Stop and Remove the Container
When we're finished:
docker stop fastapi-app
The container is now stopped.
To remove it:
docker rm fastapi-app
Notice that removing the container does not necessarily mean removing the image.
Our image still exists:
docker images
We can use that image to create another container.
One Image, Multiple Containers
This is another Docker concept that became clearer after doing this project.
We can have:
FastAPI Image
│
┌───────┼───────┐
↓ ↓ ↓
Container Container Container
1 2 3
The image is the template.
The containers are the running instances.
The Final Project Structure
After everything is set up:
fastapi-docker/
│
├── app.py
├── requirements.txt
└── Dockerfile
And the Docker workflow is:
Dockerfile
│
↓
docker build
│
↓
Docker Image
│
↓
docker run
│
↓
Docker Container
│
↓
Port 8000
│
↓
FastAPI API
What Actually Changed?
Before Docker:
My Computer
│
├── Python
├── FastAPI
├── Uvicorn
└── Application
After Docker:
Docker Container
│
├── Python
├── FastAPI
├── Uvicorn
└── Application
The application and its environment are now packaged together.
That is one of the biggest reasons Docker is useful.
A Small Lesson I Learned
One thing I learned from this experiment is that Docker isn't really about memorizing commands.
It's about understanding the relationship between the pieces.
At first, these commands looked unrelated:
docker build
docker images
docker run
docker ps
docker logs
docker stop
After actually building an application, they started to form a workflow:
Build
↓
Image
↓
Run
↓
Container
↓
Inspect
↓
Debug
↓
Stop
That mental model is much more useful than memorizing commands individually.
What's Missing From This Setup?
This is still a very simple application.
A real application would probably need:
- Environment variables
- A database
- Docker Compose
- A
.dockerignore - Better dependency management
- Production configuration
- Health checks
- Logging
- Reverse proxy
- HTTPS
And that's exactly where I want to go next.
What's Next?
The next step is to move beyond a single container.
Imagine:
FastAPI
│
┌────────┼────────┐
↓ ↓ ↓
PostgreSQL Redis Nginx
Now we have multiple services that need to communicate with each other.
That's where Docker Compose becomes useful.
Final Thoughts
Docker made much more sense to me after I stopped learning it only through commands and actually put an application inside a container.
The process was surprisingly straightforward:
Write the application
↓
Create requirements.txt
↓
Write Dockerfile
↓
Build image
↓
Run container
↓
Map the port
↓
Access the API
If you're learning Docker, I would recommend doing the same thing.
Don't just memorize:
docker run
Build something small.
Break it.
Read the logs.
Fix it.
Then build it again.
That's when Docker starts to become a tool rather than just another technology you're trying to learn.
What About You?
Have you Dockerized a project before?
If you're learning Docker right now, what's the part you're struggling with?
Let me know in the comments.
Related Articles
Part 1: From Windows to WSL (Ubuntu) + Docker
Part 2: Docker for Beginners: Images, Containers, Ports, and Volumes Explained
Part 3: I Dockerized My First FastAPI Application
More coming as I continue building and learning.
Top comments (0)