Containerize a Python App with Docker
Build a multi-container Flask and Redis app using Docker Compose.
Introduction
30 Second Summary
Every time you set up a project on a new machine, you run into missing dependencies, version conflicts, and "works on my machine" problems. Containers solve this by packaging your app and everything it needs into a single portable unit.
In this project, you will containerize a Python web app with a Redis backend using Docker Compose. You will write a Dockerfile from scratch, orchestrate multiple containers with one command, and persist data across restarts.
What You'll Build
A hit-counter web application that tracks page visits using Flask and Redis, fully containerized with Docker Compose.
By the end of this project, you'll have:
- A custom Dockerfile that builds a Python app into a Docker image from scratch.
- A compose.yaml that orchestrates two services (web + Redis) with health checks, environment variables, and volume persistence.
- A working multi-container application you can start with one command and tear down with another.
- Secret Mission: Add Compose Watch for live code reloading without rebuilding.
Are there any prerequisites?
You need Docker Desktop installed on macOS and a code editor of your choice. Basic programming familiarity is helpful, but the app code is intentionally minimal (under 15 lines of Python) so all your effort goes toward learning containerization.
Verify Docker and Set Up the Project
Before you can containerize anything, Docker needs to be running on your machine. Docker manages the entire lifecycle of your containers, from building images to running services.
In this step, you'll confirm Docker is operational and then create all the project files your container will need. By the end, you'll have a complete project directory ready to be built into a Docker image.
In this step, get ready to:
- Verify Docker Desktop is running and responsive.
- Create the project directory and application files.
- Create the Dockerfile and supporting configuration files.
Open Terminal and verify Docker is running
Docker Desktop runs the Docker daemon in the background. You need the daemon active before any docker commands will work.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Verify Docker is running by running this command:
docker version
✔️ I see Client and Server info
Docker Desktop is running and responsive. You should see version numbers for both the Client and Server sections. Move on to the next command below.
ⓧ I see "Cannot connect to the Docker daemon"
This means the Docker daemon is not running yet. Open Docker Desktop from your Applications folder (or search for it with Cmd+Space).
Wait for the whale icon to appear in your menu bar at the top of the screen. Once it stops animating, run docker version again.
- Confirm Docker Compose is available by running:
docker compose version
You should see output like Docker Compose version v2.39.4 (your exact version number may differ).
Command not found?
If docker compose version returns "command not found", make sure you are running docker compose (two words, no hyphen). The older docker-compose (hyphenated) command is deprecated.
If the issue persists, help me troubleshoot my Docker Compose installation.
Create the project directory and application files
Your project needs a dedicated folder to hold the application code and Docker configuration. You'll create this on your Desktop so it's easy to find.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir compose-demo
cd compose-demo
- Confirm the folder exists by running:
pwd
You should see /Users/your-username/Desktop/compose-demo in the output.
- Open the compose-demo folder in your code editor.
- Create a new file called app.py and paste the following code:
import os
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
)
@app.route("/")
def hello():
count = cache.incr("hits")
return f"Hello from Docker! I have been seen {count} time(s).\n"
What does this code do?
This is a minimal Flask web application that counts page visits.
- cache = redis.Redis(...) connects to a Redis server using environment variables for the host and port.
- cache.incr("hits") increments a counter in Redis every time someone visits the page.
- The app reads REDIS_HOST and REDIS_PORT from environment variables, making it easy to configure without changing code.
- Create a new file called requirements.txt and paste the following:
flask
redis
What is requirements.txt?
This file lists the Python packages your app needs. Docker will use it to install flask and redis inside the container during the image build.
- Save both app.py and requirements.txt.
Not sure where to save files?
Make sure you are saving files directly inside the compose-demo folder on your Desktop. Do not create any subfolders. All project files live at the top level of this directory.
If you need help, help me create files in the correct project directory.
✔️ Awesome, I've got everything!
Great. Make sure both files are saved before moving on.
ⓧ I'd like to double check the full code
Here is app.py:
import os
import redis
from flask import Flask
app = Flask(__name__)
cache = redis.Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
)
@app.route("/")
def hello():
count = cache.incr("hits")
return f"Hello from Docker! I have been seen {count} time(s).\n"
Here is requirements.txt:
flask
redis
Create the Docker configuration files
Docker needs a set of configuration files to know how to build your image and what to include. The most important is the Dockerfile, which is a step-by-step recipe for constructing your container image.
- Create a new file called Dockerfile (no file extension) in the compose-demo folder and paste the following:
# syntax=docker/dockerfile:1
FROM python:3.12-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["flask", "run", "--debug"]
What does each line in the Dockerfile do?
- FROM python:3.12-alpine starts from a lightweight Python image based on Alpine Linux.
- WORKDIR /code sets the working directory inside the container to /code.
- ENV lines set environment variables that Flask uses to find and run your app.
- RUN apk add ... installs system-level C libraries that Python packages need to compile.
- COPY requirements.txt . then RUN pip install installs Python dependencies before copying app code. This layer ordering means Docker can cache the dependency install step.
- COPY . . copies your application code into the image.
- EXPOSE 5000 documents which port the container listens on.
- CMD ["flask", "run", "--debug"] defines the command that runs when the container starts.
- Save Dockerfile.
File has a .txt extension?
Some editors automatically add .txt to new files. Make sure the file is named exactly Dockerfile with no extension. In most editors, you can rename it by right-clicking the file in the sidebar.
If you are unsure, help me check that my Dockerfile has no extension.
- Create a new file called .env and paste the following:
APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
What is a .env file?
A .env file stores configuration values outside your code. Docker Compose automatically reads this file and substitutes the values into your compose.yaml (which you'll create in the next step).
This keeps sensitive or environment-specific values separate from your application logic.
- Save .env.
- Create a new file called .dockerignore and paste the following:
.env
*.pyc
__pycache__
redis-data
What is .dockerignore?
When Docker builds your image with COPY . ., it copies everything in the project folder. The .dockerignore file tells Docker which files to skip.
This keeps your .env secrets out of the image, excludes Python cache files, and prevents stale data from being bundled in.
- Save .dockerignore.
- Confirm all files are in place by switching back to your Terminal and running:
ls -a
You should see app.py, requirements.txt, Dockerfile, .env, and .dockerignore in the output.
Missing a file?
If a file is missing from the ls -a output, check that you saved it inside the compose-demo folder (not a subfolder or your Desktop). Files starting with a dot (.env, .dockerignore) are hidden by default. The -a flag shows them.
If you need help, help me find where my files were saved.
✔️ Awesome, I've got everything!
Great. Double check all files are saved before moving on.
ⓧ I'd like to double check the full code
Here is Dockerfile:
# syntax=docker/dockerfile:1
FROM python:3.12-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["flask", "run", "--debug"]
Here is .env:
APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
Here is .dockerignore:
.env
*.pyc
__pycache__
redis-data
Your project structure is ready. Next up, you'll define your services in a compose.yaml file and launch the entire application stack with a single command.
Define Services and Launch with Docker Compose
Your project files are ready. The Dockerfile knows how to build the Flask app into an image, and the .env file holds your configuration values.
Now you need a way to start both your web app and its Redis database together with a single command. Docker Compose reads a compose.yaml file that defines all the services your app needs, then builds, creates, and starts everything at once.
In this step, get ready to:
- Create a compose.yaml file with two services (web and Redis).
- Build and launch the multi-container stack.
- Verify the app works and tear it down.
Create compose.yaml defining two services
A compose.yaml file declares each service your application needs. Each service is either built from a local Dockerfile or pulled as a pre-built image from Docker Hub.
- Create a new file called compose.yaml in your compose-demo folder and add this content:
# Define the services that make up your application
services:
# Web service: your Flask hit-counter app
web:
build: . # Build the image from the Dockerfile in this directory
ports:
- "${APP_PORT}:5000" # Map host port (from .env) to container port 5000
environment:
- REDIS_HOST=${REDIS_HOST} # Pass Redis connection info to the container
- REDIS_PORT=${REDIS_PORT}
# Redis service: the in-memory data store
redis:
image: redis:alpine # Pull the lightweight Redis image from Docker Hub
What does this file do?
The web service uses build: . to build an image from the Dockerfile in the current directory. It maps port 8000 on your machine (pulled from APP_PORT in .env) to port 5000 inside the container where Flask listens.
The redis service uses image: redis:alpine to pull a pre-built lightweight Redis image. No build needed since the official image is ready to use.
Docker Compose automatically reads variables from .env and substitutes them wherever it sees the ${VARIABLE} syntax in your YAML file.
- Save compose.yaml.
- Confirm the file exists by running:
ls
You should see compose.yaml listed alongside app.py, Dockerfile, requirements.txt, .env, and .dockerignore.
Don't see compose.yaml in the list?
Make sure you saved the file in the compose-demo directory, not a parent or sub-folder. Run pwd to check that your terminal is inside compose-demo.
Also confirm the filename is exactly compose.yaml (not compose.yml or docker-compose.yaml).
Why isn't my compose.yaml showing up?
✔️ Awesome, I've got everything!
Great. Double check you saved the file, then move on to starting the stack.
ⓧ I'd like to double check the full code
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
redis:
image: redis:alpine
Start the application stack
With your compose.yaml in place, a single command builds the web image, pulls the Redis image, creates a network, and starts both containers.
- Launch the entire stack by running:
docker compose up
What happens when you run this?
Docker Compose reads compose.yaml, builds your web image from the Dockerfile, and pulls redis:alpine from Docker Hub. It then creates a default network for the project and starts both containers.
Because you did not use the -d flag, the command runs in attached mode. You will see live logs from both services streaming in your terminal, colour-coded by service name.
The first run takes a minute or two while Docker downloads image layers and installs your Python dependencies.
- Once you see log lines from the web service showing Flask is running, open your browser and go to http://localhost:8000.
- Refresh the page a few times. Each refresh increments the counter.
You should see the message: "Hello from Docker! I have been seen X time(s)." The number increases on each refresh.
Browser shows "connection refused" or nothing loads?
Check that your terminal shows Flask running with no error messages. If you see a connection error mentioning Redis, that is the startup race condition you will fix in the next step. Try pressing Ctrl+C, then run docker compose up again.
If port 8000 is already in use on your machine, you will see a bind error. Close any other process using that port, or change APP_PORT in your .env file to another value like 8080.
Help me debug why my app won't load at localhost:8000
Tear down the stack
Now that you have confirmed the app works, stop everything and clean up the containers. This demonstrates how Docker Compose manages the full lifecycle.
- Switch back to your terminal and press Ctrl+C to stop the attached logs.
- Remove the containers and network by running:
docker compose down
What does docker compose down do?
This command stops both containers, removes them, and deletes the network Compose created. The built image stays on disk so the next docker compose up starts faster.
The important detail: because the containers are removed, any data Redis stored in its writable layer is gone. If you start the stack again, the counter resets to zero. You will fix this with named volumes in a later step.
- Verify by running docker compose up again briefly and refreshing http://localhost:8000. The counter starts back at 1.
- Press Ctrl+C and run docker compose down again to clean up.
Why does the counter reset?
Docker containers have a writable layer that holds any data created at runtime. When docker compose down removes the container, that layer is deleted. This is expected behaviour for stateless services, but not ideal for databases.
Your multi-container app is working. Next up, you will add health checks so the web service waits for Redis to be ready before connecting.
Add Health Checks and Fix the Startup Race
Your two-container app is up and running. But there's a hidden reliability problem lurking in the current setup.
When Docker Compose starts both containers simultaneously, the web app might try to connect to Redis before Redis is ready to accept connections. This causes a crash. In this step, you'll add a health check that proves Redis is ready, and tell Compose to wait for that proof before starting the web container.
In this step, get ready to:
- Understand why a startup race condition happens between containers.
- Add a health check to the Redis service and a dependency condition to the web service.
- Verify that Redis reaches "Healthy" before the web app starts.
Understand the startup race
Right now, both the web and redis services start at the same time. Compose only waits for a container to be running, not for its internal process to be ready.
If Redis takes a moment longer to initialize, the Flask app tries to connect to a port that isn't listening yet. The result is a connection error and a crashed container.
What is a health check?
A health check is a command that Docker runs at regular intervals inside a container to verify the service is actually working. Once the check passes, Docker marks the container as "Healthy".
You can then configure a dependent service to wait for that healthy status before starting. This eliminates the race condition entirely.
Add a healthcheck and depends_on condition
The fix has two parts. First, you define a health check on the Redis container using redis-cli ping. Second, you tell the web service to wait until Redis is healthy before starting.
- Open compose.yaml in your code editor.
- Replace the entire contents of the file with the following updated configuration:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
# Wait for Redis to pass its health check before starting
depends_on:
redis:
condition: service_healthy
redis:
image: redis:alpine
# Verify Redis is ready to accept commands
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
What does this configuration do?
- healthcheck.test runs redis-cli ping inside the Redis container every 5 seconds. If Redis responds with PONG, the check passes.
- interval sets how often Docker runs the check. timeout sets how long to wait for a response before considering it failed.
- retries means Docker allows 5 consecutive failures before marking the container as unhealthy.
- start_period gives Redis 10 seconds of grace time during startup where failed checks don't count against retries.
- depends_on with condition: service_healthy tells Compose to hold the web service until Redis passes its health check.
- Save compose.yaml.
Seeing a YAML syntax error?
YAML is sensitive to indentation. Make sure healthcheck is indented at the same level as image under the redis service. Use spaces, not tabs.
help me fix a YAML indentation error in my compose.yaml healthcheck configuration
Verify the startup order
Now you'll run the stack and watch the output to confirm Redis becomes healthy before the web service starts.
- Start the application stack by running:
docker compose up
- Watch the terminal output. You should see a line showing the redis container marked as Healthy before the web container shows Started.
- Open http://localhost:8000 in your browser and refresh a few times to confirm the counter still increments.
What should I see in the terminal?
The output will show Docker creating the containers, then the redis container reaching "Healthy" status. Only after that will the web container transition to "Started". This proves the startup ordering is working correctly.
- Stop the stack by pressing Ctrl+C in the terminal, then tear down by running:
docker compose down
Web container exiting immediately?
If the web container exits with a connection error, double-check that condition: service_healthy is nested under redis: inside depends_on. The indentation must be exactly two spaces deeper at each level.
help me debug why my web container crashes before Redis is ready despite adding depends_on
✔️ Awesome, I've got everything!
Great. Double check you've saved compose.yaml and the stack is torn down before moving on.
ⓧ I'd like to double check the full code
Here is the complete compose.yaml you should have at this point:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
redis:
condition: service_healthy
redis:
image: redis:alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
Your containers now start in the right order every time. Next up, you'll tackle data persistence so your counter survives a restart.
Persist Data with Named Volumes
Your health checks solved the startup race condition. Both containers now start in the correct order every time.
There's still a gap though. Every time you run docker compose down, Docker removes the containers and all their data. Your hit counter resets to zero.
Named volumes solve this by storing data on your host machine, outside the container lifecycle. In this step, you'll configure a volume for Redis so your counter survives restarts.
In this step, get ready to:
- Understand why container data disappears on teardown.
- Add a named volume to your compose.yaml.
- Verify that data persists across container restarts.
Understand the data loss problem
Containers have a writable layer where all changes (like Redis saving counter data) are stored. When you run docker compose down, Docker removes the containers and their writable layers entirely.
What is a named volume?
A named volume is a storage area that Docker manages on your host machine. It lives outside the container, so removing a container doesn't touch the volume's data.
When you attach a named volume to a container, Docker mounts that storage into the container's filesystem at a path you specify. The container reads and writes data there as if it were a normal directory.
Add volume configuration to compose.yaml
You need two additions to compose.yaml: a volume mount on the redis service and a top-level volume declaration.
- Open compose.yaml in your code editor.
- Replace the entire file contents with this updated configuration:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
redis:
condition: service_healthy
redis:
image: redis:alpine
volumes:
- redis-data:/data # Mount the named volume to Redis data directory
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
# Declare the named volume so Docker manages it
volumes:
redis-data:
What did you add?
- volumes: - redis-data:/data under the redis service mounts a named volume called redis-data to the /data directory inside the container. This is where Redis stores its persistence files.
- The top-level volumes: block declares redis-data as a named volume managed by Docker. Without this declaration, Docker Compose would reject the file.
- Save compose.yaml.
Seeing a YAML syntax error?
- Check your indentation. The volumes: key under redis must be indented 4 spaces (same level as image: and healthcheck:).
- The top-level volumes: must have zero indentation, at the same level as services:.
- Make sure redis-data: under the top-level volumes: has a colon and nothing else after it.
Help me fix a YAML indentation error in my compose.yaml volume configuration.
Test persistence across restarts
Time to prove that your volume works. You'll start the stack, generate some counter data, tear it down, and restart to see the count survive.
- Start the stack in detached mode by running this command:
docker compose up -d
- Open http://localhost:8000 in your browser.
- Refresh the page 4-5 times to build up the counter.
- Note the final number you see (for example, 5).
Now tear the stack down and bring it back up to test if data persists.
- Stop and remove the containers by running:
docker compose down
- Start the stack again by running:
docker compose up -d
- Refresh http://localhost:8000 in your browser.
The counter continues from where it left off. Your data survived the restart because the named volume kept the Redis data safe on your host machine.
Counter reset to 1?
- Make sure you ran docker compose down (without -v). The -v flag removes volumes, which deletes the persisted data.
- Check that redis-data:/data is under the redis service, not the web service.
- Confirm the top-level volumes: declaration exists at the bottom of your file.
Help me figure out why my Docker volume isn't persisting data across restarts.
Finally, let's see how to intentionally wipe the volume when you want a fresh start.
- Remove containers, networks, and the named volume by running:
docker compose down -v
What does the -v flag do?
The -v flag tells Docker Compose to also remove any named volumes declared in the file. Without it, docker compose down leaves volumes intact. This gives you control over when data resets.
✔️ Awesome, I've got everything!
Great. Double check you've saved compose.yaml with the volume configuration.
ⓧ I'd like to double check the full code
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
depends_on:
redis:
condition: service_healthy
redis:
image: redis:alpine
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
volumes:
redis-data:
Your data now survives restarts. Next up, you'll learn how to inspect and debug your running stack using built-in Docker Compose commands.
Inspect and Debug Your Running Stack
Your containers now have persistent storage, health checks, and ordered startup. But in a real project, you will not always have the luxury of watching the terminal output scroll by in the foreground.
You need tools to peek inside running containers, verify their configuration, and read their logs. Docker Compose provides several built-in commands for exactly this.
In this step, get ready to:
- Inspect the fully resolved Docker Compose configuration.
- Stream live logs from running containers.
- Execute commands inside running containers to debug.
Inspect the resolved configuration and stream logs
Your compose.yaml uses variable substitution from the .env file. The resolved configuration command shows you the final YAML that Docker actually applies, with all variables filled in.
- View the fully resolved configuration by running this command:
docker compose config
What does this command do?
This prints the final YAML configuration with all ${...} placeholders replaced by their actual values from .env. You should see 8000:5000 in the ports section and REDIS_HOST=redis in the environment section.
This is useful for catching typos in variable names. If a variable is misspelled, the resolved output shows an empty string instead of the expected value.
- Start the application stack in detached mode by running this command:
docker compose up -d
The -d flag runs containers in the background and gives you your terminal back. You should see both the redis and web containers start.
Containers didn't start?
If you see a port conflict error, another process is already using port 8000. Stop the other process or change APP_PORT in your .env file to a different port like 8080.
help me fix a Docker Compose port conflict
- Open http://localhost:8000 in your browser and refresh a few times to generate some hit counter data.
- Stream live logs from both services by running this command:
docker compose logs -f
You should see color-coded log output from both the web and redis services. Each line shows the service name followed by its log message.
- Press Ctrl+C to stop following the logs.
What does the -f flag do?
The -f flag means "follow." It keeps the log stream open so new messages appear in real time. Without -f, Docker prints the existing logs and returns you to the prompt immediately.
Execute commands inside running containers
Sometimes you need to inspect what is happening inside a specific container. The docker compose exec command lets you run any command inside a running container as if you had a shell open inside it.
- Verify the environment variables inside the web container by running this command:
docker compose exec web env | grep REDIS
You should see two lines in the output: REDIS_HOST=redis and REDIS_PORT=6379. This confirms that the environment variables from your compose.yaml made it into the running container.
What does this command do?
This runs env inside the web container to list all environment variables, then pipes the output through grep REDIS on your host machine to filter for just the Redis-related ones.
- Inspect the live counter value stored in Redis by running this command:
docker compose exec redis redis-cli GET hits
You should see a number matching how many times you refreshed the page. This proves that your Python app is writing data to Redis, and you can query that data directly.
Why is this useful?
In real-world debugging, you often need to check whether data actually reached the database or if the problem is in the application layer. Running queries directly inside the database container gives you that answer instantly.
Seeing (nil) instead of a number?
This means no hits have been recorded yet. Open http://localhost:8000 in your browser and refresh at least once, then run the redis-cli GET hits command again.
help me debug why my Redis counter shows nil
- Tear down the stack by running this command:
docker compose down
This stops and removes the containers and network. Your named volume persists because you did not pass the -v flag.
Secret mission
Add Compose Watch for Live Code Reloading
Every time you change your Python code, you have to rebuild the image and restart the container. What if Docker could detect file changes and reload automatically? In this secret mission, you'll add Compose Watch to get instant feedback during development.
Clean Up Your Resources
Clean Up Your Resources
This project runs entirely locally, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Resources you used:
- Docker images built during the project (local, no cost).
- Docker volume redis-data storing Redis persistence data (local, no cost).
- Docker networks created by Compose (local, no cost).
- The compose-demo project folder on your Desktop.
Keep everything running
No action needed. Choose this if you're still actively building or experimenting with Docker Compose.
- Your stack continues running in the background and uses minimal system resources.
- If you closed the terminal, restart the stack by navigating to the compose-demo folder and running docker compose up -d.
Pause - I'll come back to this later
Stop the running containers without removing them. Your data persists in the named volume, and you can restart quickly later.
- If your terminal is still showing Compose Watch output, press Ctrl+C to stop the process.
- Stop the containers by running:
docker compose stop
- When you're ready to resume, navigate back to the compose-demo folder and run docker compose start.
Delete - I don't want to use this again
Remove all project resources and start fresh. This frees all disk space used by images, volumes, and the project folder.
- If your terminal is still showing Compose Watch output, press Ctrl+C to stop the process.
- Remove all containers, volumes, networks, and built images by running:
docker compose down -v --rmi all
- Navigate out of the project folder and delete it by running:
cd ~/Desktop
rm -rf compose-demo
- Confirm the folder is gone by running ls. You should no longer see compose-demo in the output.
Nice Work!
Nice Work!
You just containerized a multi-service Python application from scratch using Docker and Docker Compose. That is a core skill tested in DevOps and backend engineering interviews.
You learned how to:
- Write a Dockerfile from scratch and build a custom Docker image for a Python Flask application using a lightweight Alpine base.
- Orchestrate a multi-container application (web + Redis) with Docker Compose, including health checks, environment variables, and startup dependency conditions.
- Persist data across container restarts using named volumes, and inspect running containers with docker compose exec and docker compose logs.
- Secret Mission: Added Compose Watch for automatic live reloading during development.
Ready to quiz yourself?