Your FastAPI service from Chapter 4 is running in a container. It responds to requests, returns JSON, and everything works. Then you deploy it to a server. It crashes. No error on your screen, no stack trace, nothing. The container simply stops.
This is where container debugging skills become essential. Unlike local development where errors appear in your terminal, containerized applications fail silently unless you know where to look. The container's logs, its internal state, its configuration, and its resource usage are all hidden behind Docker's abstraction layer.
In this lesson, you'll learn the debugging toolkit that every container developer needs: reading logs to understand what happened, executing commands inside containers to inspect their state, and using inspection tools to verify configuration. You'll practice these skills using the FastAPI application you built in Chapter 4, intentionally breaking it to develop debugging intuition.
Before debugging, let's run your Chapter 4 FastAPI container in the background. Navigate to your task-api directory from Chapter 4:
Output:
Now run it in detached mode (-d) so it runs in the background:
Output:
The long string is the container ID. The -d flag means "detached"—the container runs in the background and you get your terminal back.
Verify the container is running:
Output:
Test that it's responding:
Output:
Now you have a running container to debug. Let's explore the debugging tools.
The most important debugging command is docker logs. It shows everything your application writes to stdout and stderr—print statements, uvicorn startup messages, errors, and stack traces.
View logs from your running container:
Output:
These logs show uvicorn started successfully. Now make a request and check logs again:
Output:
The new log line shows the request: the client IP, the endpoint accessed, and the HTTP response code (200 OK).
For live debugging, use the -f (follow) flag to stream logs continuously:
Output:
Now in another terminal, make requests and watch them appear in real-time. Press Ctrl+C to stop following.
For large log files, use --tail to see only the last N lines:
Output:
Now let's create a container that fails on startup. Stop and remove the current container:
Output:
Create a Python script that simulates a startup failure. Create broken_main.py:
Create a Dockerfile for this broken app. Create Dockerfile.broken:
Build and run it:
Output:
Check if it's running:
Output:
Empty! The container isn't running. Check all containers, including stopped ones:
Output:
Status shows "Exited (1)" - the container crashed with exit code 1. Now use docker logs to find out why:
Output:
The logs tell you exactly what went wrong: the API_KEY environment variable is missing.
Now that you know the problem, run it correctly with the environment variable:
Output:
The container now starts successfully because the required environment variable is set.
Logs show what happened, but sometimes you need to inspect the container's current state. docker exec lets you run commands inside a running container.
First, restart your working FastAPI container:
Output:
Now execute commands inside the running container:
Output:
The container's working directory is /app, as set by WORKDIR in the Dockerfile.
List files in the container:
Output:
Check what user is running the process:
Output:
For deeper debugging, launch an interactive shell inside the container:
Output:
The -it flags mean "interactive" and "allocate a TTY" (terminal). You're now inside the container. Try some commands:
Output:
The exit command returns you to your host machine.
You can even test your API from inside the container:
Output:
This confirms the API is responding on port 8000 inside the container. If this works but external requests fail, you have a port mapping problem, not an application problem.
The docker inspect command shows complete configuration and runtime state in JSON format. This is essential for verifying that a container was started with the correct settings.
Inspect your running container:
This outputs hundreds of lines. Let's extract specific information.
Output:
Output:
This confirms exactly what command the container is running.
Output:
Output:
This shows port 8000 in the container is mapped to port 8000 on the host.
For crashed containers, inspect shows why they stopped:
Output:
Exit code 1 means the application exited with an error. Exit code 0 means success. Exit code 137 means the kernel killed the process (usually out of memory).
A common debugging scenario: you try to start a container and it fails because the port is already in use.
Try to start another container on port 8000 while task-api is running:
Output:
The error is clear: port 8000 is already allocated (by your first container).
Output:
Now you have two containers:
Verify both work:
Output:
If you need port 8000 specifically, find what's using it:
Output:
Stop the container using your desired port:
Now you can start a new container on port 8000.
Clean up for the next section:
Containers can crash due to bugs, resource exhaustion, or temporary failures. Instead of manually restarting them, configure Docker to restart them automatically.
Docker supports several restart policies:
Create a container that crashes sometimes. Create flaky_main.py:
Build it:
Wait, that Dockerfile won't work for this script. Create Dockerfile.flaky:
The -u flag means unbuffered output so logs appear immediately.
Output:
Run without restart policy:
Output:
If the container crashed (30% chance), it stays dead. Remove it:
Now run with automatic restart:
Watch it recover from failures:
Output (if it fails and restarts):
The container automatically restarted after the failure. Check restart count:
Output:
For production services, use --restart=unless-stopped. This ensures:
Clean up:
Now that you understand container debugging fundamentals, practice these skills with increasingly complex scenarios.
Create a FastAPI application that requires a DATABASE_URL environment variable. Run it without the variable and use the debugging tools you learned to identify the problem:
Create a FastAPI app that:
Help me create the Dockerfile and show me how to:
What you're learning: This reinforces the pattern of using docker logs and docker inspect --format='{{.State.ExitCode}}' to diagnose startup failures, and -e to provide environment variables.
Sometimes your application seems to start but doesn't respond to requests. Practice debugging this:
My FastAPI container starts successfully (docker logs shows "Uvicorn running") but curl http://localhost:8000/ returns "Connection refused".
Help me debug this using:
What you're learning: This teaches you to systematically isolate whether the problem is the application, the port mapping, or network configuration using docker exec to test from inside the container.
Production services need to handle crashes gracefully:
I have a FastAPI service that occasionally crashes due to memory pressure. Help me configure it with:
Show me the docker run command and how to verify the configuration.
What you're learning: This reinforces restart policies and introduces memory limits (--memory), which become critical when deploying AI services that can consume large amounts of RAM.
[!NOTE] Safety note: When debugging containers in production, use read-only commands (docker logs, docker inspect) before interactive commands (docker exec). Avoid running shells in production containers unless absolutely necessary, as it can affect running services.
You built a docker-deployment skill in Chapter 1. Test and improve it based on what you learned.
Using my docker-deployment skill, diagnose a failed container startup. Does my skill include debugging commands like docker logs, docker exec, and docker inspect?
Ask yourself:
If you found gaps:
My docker-deployment skill is missing debugging and troubleshooting capabilities. Update it to include docker logs, docker exec, docker inspect usage, and restart policy configuration.