Containerize a Python App with Docker Compose
Orchestrate a Flask and Redis app using Docker Compose with health checks.
Introduction
30 Second Summary
Every app you use online talks to other services behind the scenes. A website might connect to a database, a cache, a message queue, and more. Getting all these pieces to run together reliably is one of the biggest challenges in software development.
In this project, you will containerize a Python web application and orchestrate it alongside a Redis cache using Docker Compose. You will define services, wire them together, and set up a live development workflow that updates your app instantly as you code.
What You'll Build
A hit-counter web application that tracks page visits using Redis, fully containerized and orchestrated with Docker Compose to demonstrate a real production workflow.
By the end of this project, you'll have:
- A Dockerfile that packages a Python Flask app into a container image.
- A compose.yaml that orchestrates a web service and a Redis service with health checks, startup ordering, and persistent data volumes.
- A live development workflow using Compose Watch that auto-syncs code changes without manual rebuilds.
- Secret Mission: Modularize your stack by splitting services across multiple Compose files using the include directive.
Are there any prerequisites?
This project assumes you have Docker Engine installed on Ubuntu and are comfortable with basic command line usage. You do not need Python installed on your machine because Python runs inside the container.
Create the Project Files
Your project directory is ready. Now you need the actual application code that Docker will package into a container image.
You'll create a Flask web app that counts page visits using Redis, then write the Dockerfile and configuration files Docker needs to build and run it.
In this step, get ready to:
- Create the Flask hit-counter application and its dependencies file.
- Write a Dockerfile to package the app into a container image.
- Add configuration files for the build context and environment variables.
Create the application files
The app is a simple hit counter. Every time someone visits the page, Flask increments a value stored in Redis and displays the updated count.
- In your text editor, create a new file called app.py inside your compose-demo folder and add 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?
The app creates a Flask web server and connects to Redis using hostname and port values pulled from environment variables. The defaults (redis and 6379) match the service name and port that Docker Compose will configure later.
The hello() function runs every time someone visits the root URL. It increments a Redis key called hits and returns the updated count.
- Save app.py.
- Create another new file called requirements.txt in the same folder and add these two pinned dependencies:
flask==3.1.3
redis==5.3.1
Why pin versions?
Pinning exact versions (flask==3.1.3, redis==5.3.1) ensures your container installs the same packages every time it builds. Without pins, a future build could pull a breaking update and silently break your app.
- Save requirements.txt.
- Confirm both files exist by running this in your terminal:
ls
You should see app.py and requirements.txt in the output.
Don't see both files?
Make sure you saved each file inside the compose-demo directory, not a parent or subfolder. Run pwd to confirm your terminal is in the right place.
help me find where my files were saved
Create the Dockerfile and supporting files
A Dockerfile is a recipe that tells Docker how to build your application into a container image. Each line is a build step that Docker executes in order.
- Create a new file called Dockerfile (no file extension) in your compose-demo folder and add 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 do?
- # syntax=docker/dockerfile:1 tells Docker to use the latest stable Dockerfile parser, giving you access to the newest build features regardless of your Docker Engine version.
- FROM python:3.12-alpine starts from a lightweight Python image based on Alpine Linux.
- RUN apk add --no-cache gcc musl-dev linux-headers installs C compilers needed to build Python packages with C extensions (like Flask's dependencies).
- COPY requirements.txt . copies your dependencies file first, then RUN pip install installs them. Docker caches this layer, so rebuilds are fast unless dependencies change.
- COPY . . copies the rest of your source code into the image.
- CMD ["flask", "run", "--debug"] sets the default command that runs when the container starts.
- Save Dockerfile.
File named Dockerfile.txt by accident?
Some text editors automatically add a .txt extension. Make sure the file is named exactly Dockerfile with no extension. You can verify by running ls in your terminal.
help me rename my Dockerfile
Next, you need two small configuration files. The .dockerignore file tells Docker which files to skip when copying your project into the image. The .env file stores configuration values that Docker Compose reads automatically.
- Create a file called .dockerignore in your compose-demo folder and add:
.env
*.pyc
__pycache__
redis-data
Why exclude .env from the image?
The .env file contains configuration for Docker Compose to read at runtime. Baking it into the image would hardcode values that should stay flexible across environments.
- Save .dockerignore.
- Create one more file called .env in the same folder and add:
APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
What are these values for?
- APP_PORT=8000 sets which port on your machine maps to the container's Flask server.
- REDIS_HOST=redis tells the Flask app to connect to a host named redis. Docker Compose makes this hostname available automatically by matching the service name.
- REDIS_PORT=6379 is the default port Redis listens on.
- Save .env.
- Confirm all five files exist by running:
ls -a
You should see app.py, requirements.txt, Dockerfile, .dockerignore, and .env in the output.
Why ls -a?
Files starting with a dot (.dockerignore, .env) are hidden by default on Linux. The -a flag tells ls to show all files, including hidden ones.
Missing one or more files?
Double check that each file was saved inside compose-demo, not in a subfolder or your home directory. Files starting with a dot may not appear in your editor's sidebar unless you enable hidden file visibility.
help me find my missing project files
✔️ Awesome, I've got everything!
Great. Double check you have saved all five files in your compose-demo directory.
ⓧ I'd like to double check the full code
Here is the complete content of each file for comparison.
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"
flask==3.1.3
redis==5.3.1
# 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"]
.env
*.pyc
__pycache__
redis-data
APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
All your project files are in place. Next up, you'll create the Docker Compose configuration that wires these files into a running multi-container application.
Define and Launch Your Multi-Container App
Your project files are ready. You have a Flask app, a Dockerfile, and environment configuration all sitting in the compose-demo folder.
Now it's time to tell Docker Compose how to run multiple containers together. Compose uses a single YAML file to define all the services in your application and how they connect to each other.
In this step, get ready to:
- Create a compose.yaml file that defines two services (web and Redis).
- Launch the multi-container stack and verify the hit counter works.
- Observe two problems: data loss on restart and a startup race condition.
Create compose.yaml with two services
Docker Compose reads a file called compose.yaml to understand which containers to run and how they relate to each other. Your file will define two services: a Flask web app and a Redis data store.
- Create a new file called compose.yaml inside your compose-demo folder with the following content:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
redis:
image: redis:alpine
What does this configuration do?
The services key is where you define each container in your application.
- web uses build: . to build an image from your local Dockerfile. The ports mapping connects your host's port 8000 (from APP_PORT in .env) to port 5000 inside the container where Flask listens.
- environment passes REDIS_HOST and REDIS_PORT into the web container so your Python code can find Redis.
- redis uses image: redis:alpine to pull a pre-built Redis image from Docker Hub instead of building from a Dockerfile.
How does variable interpolation work?
The ${APP_PORT} syntax tells Compose to substitute values from your .env file. Compose automatically reads .env in the same directory and makes those values available for interpolation in compose.yaml.
- Save compose.yaml.
- Confirm the file exists by running:
ls compose.yaml
You should see compose.yaml listed in the output.
Don't see the file?
Make sure you created the file in the compose-demo directory, not a subfolder. Run pwd to check your current location.
Help me find my compose.yaml file
✔️ Awesome, I've got everything!
Great. Double check you've saved your file before moving on.
ⓧ I'd like to double check the full code
Here's what your complete compose.yaml should look like:
services:
web:
build: .
ports:
- "${APP_PORT}:5000"
environment:
- REDIS_HOST=${REDIS_HOST}
- REDIS_PORT=${REDIS_PORT}
redis:
image: redis:alpine
Start the stack and verify
With your Compose file saved, you can now launch both containers with a single command.
- Build and start both containers by running:
docker compose up
What does this command do?
docker compose up reads your compose.yaml, builds the web image from your Dockerfile, pulls the redis:alpine image from Docker Hub, creates a network so both containers can talk to each other, and starts both containers.
This runs in the foreground, so your terminal will show live logs from both services. You'll see build output first, then log messages as each container starts.
Wait for the logs to settle. You'll see lines from both web and redis indicating they are running.
- Open your browser and go to http://localhost:8000.
- You should see: Hello from Docker! I have been seen 1 time(s).
- Refresh the page a few times. The counter increments with each visit.
Page not loading?
Check that you see both containers running in your terminal logs. If you see an error about port 8000 already being in use, stop whatever process is using that port and try again.
If you see a connection refused error from Flask mentioning Redis, it means the web container started before Redis was ready. Press Ctrl+C and run docker compose up again.
Help me troubleshoot my hit counter page
Observe the problem and tear down
The hit counter works, but there are two hidden problems. Let's reveal them by restarting the stack.
- In the terminal where docker compose up is running, press Ctrl+C to stop the containers.
- Remove the stopped containers by running:
docker compose down
- Start the stack again by running:
docker compose up
- Refresh http://localhost:8000 in your browser.
The counter is back to 1. All your previous visit data is gone because Redis was storing it in memory inside the container. When the container was removed, the data disappeared with it.
What is the startup race condition?
There's a second problem that's harder to see. Right now, the web container and redis container start at the same time. If web starts faster than redis, Flask tries to connect to a Redis server that isn't ready yet.
This can cause a connection error on the first page load. In the next step, you'll fix both problems: data loss and the race condition.
- Press Ctrl+C to stop the containers.
- Tear down the stack completely by running:
docker compose down
- Confirm no containers are running:
docker compose ps
You should see an empty list with no running services.
Still seeing containers running?
Run docker compose down one more time. If containers persist, try docker compose down --remove-orphans to clean up any leftover services.
Help me clean up leftover containers
You've proven the multi-container stack works, but it has two clear weaknesses. Next up, you'll add health checks to fix the race condition and a named volume to make the data persist across restarts.
Add Health Checks and Data Persistence
You've got both containers running and the hit counter working. But you noticed two problems in the last step: the counter resets when you restart the stack, and the web service can crash if it starts before Redis is ready.
Docker Compose has built-in solutions for both. Health checks let a service declare when it's actually ready to accept connections. Named volumes store data outside the container's lifecycle so it persists across restarts.
In this step, get ready to:
- Add a health check to the Redis service and configure startup ordering.
- Add a named volume so Redis data survives container restarts.
- Verify that both fixes work by restarting the stack.
Add a health check and startup ordering
A health check is a command that Docker runs periodically inside a container to verify the service is responding. Once Redis reports healthy, Docker Compose can hold the web service back until it's safe to start. This eliminates the race condition where Flask tries to connect before Redis is listening.
- Open compose.yaml in your text editor.
- Find the redis service. Add this healthcheck block below image: redis:alpine:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
What does this health check do?
- test runs redis-cli ping inside the container. If Redis responds with PONG, the check passes.
- interval runs the check every 5 seconds.
- retries allows 5 consecutive failures before marking the container unhealthy.
- start_period gives Redis 10 seconds to initialize before health check failures start counting.
- Now find the web service in the same file. Add this depends_on block below the environment section:
depends_on:
redis:
condition: service_healthy
What does depends_on do here?
condition: service_healthy tells Docker Compose to wait until the Redis container's health check passes before starting the web container.
Without this, Compose only waits for the container to exist. It does not wait for Redis to actually be ready to accept connections.
- Save compose.yaml.
- You won't see a change yet. The next substep adds data persistence, and then you'll test both together.
Add a named volume for Redis persistence
By default, all data inside a container is lost when the container stops. A named volume stores data on the host machine, separate from the container's lifecycle. Redis writes its data files to /data, so mounting a named volume there keeps your hit counter alive across restarts.
- In compose.yaml, find the redis service again. Add this volumes entry between image: redis:alpine and the healthcheck block you just added:
volumes:
- redis-data:/data
- At the very bottom of compose.yaml, add a top-level volumes key to declare the named volume:
volumes:
redis-data:
Why are there two volumes entries?
The volumes under the redis service tells Docker to mount the redis-data volume at /data inside the container.
The top-level volumes key declares that redis-data is a named volume managed by Docker. Without this declaration, Compose would report an error.
- Save compose.yaml.
✔️ Awesome, I've got everything!
Great. Double check you've saved compose.yaml before moving on.
ⓧ 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:
Verify health checks and persistence
- Start the stack by running:
docker compose up
Watch the terminal output. You should see a message containing Healthy before the web container starts.
What should I see?
Look for output like Container compose-demo-redis-1 Healthy in the terminal. This confirms the health check passed and the web service was allowed to start.
- Open http://localhost:8000 in your browser.
- Refresh the page several times to build up the counter (e.g., to 5 or 6).
- Return to your terminal and stop the stack by pressing Ctrl+C.
- Restart the stack by running:
docker compose up
- Refresh http://localhost:8000 in your browser.
Your counter should continue from where it left off. The named volume kept the Redis data alive between restarts.
- Stop the stack by pressing Ctrl+C, then tear it down by running:
docker compose down
How do I reset the counter?
If you ever want a fresh start, run docker compose down -v. The -v flag removes named volumes along with the containers, so your Redis data is wiped clean.
Counter still resetting to 1?
- Make sure the volumes entry under redis is indented at the same level as image and healthcheck.
- Confirm the top-level volumes key is at the root level (no indentation), not nested under services.
- Check that the volume mount is redis-data:/data with a colon separating the volume name from the container path.
- Why is my Redis data not persisting across container restarts in Docker Compose?
Both problems are solved. Your services start in the right order and your data survives restarts. Next up, you'll set up Compose Watch for live code editing without manual rebuilds.
Enable Compose Watch for Live Development
Your stack now starts in the right order and keeps data safe across restarts. That solves the production reliability side of things.
But what about the development side? Right now, every time you change a line of Python, you have to stop the stack, rebuild the image, and restart the containers. Compose Watch eliminates that cycle by automatically syncing file changes into your running container as you save.
In this step, get ready to:
- Add a develop.watch block to compose.yaml that defines file-sync rules.
- Start the stack with Watch mode enabled.
- Make a live code change and see it reflected in the browser without any manual rebuild.
Add the develop.watch block to compose.yaml
Compose Watch uses rules defined in your compose.yaml to decide what happens when a local file changes. You will define two rules: one that syncs code changes into the container and restarts it, and one that triggers a full image rebuild when dependencies change.
- Open compose.yaml in your text editor.
- Find the depends_on block inside the web service. It currently looks like this:
depends_on:
redis:
condition: service_healthy
- Directly below that depends_on block (at the same indentation level as depends_on), add the following develop block:
develop:
watch:
- action: sync+restart
path: .
target: /code
- action: rebuild
path: requirements.txt
What does this code do?
The develop.watch block defines two rules that tell Compose how to react to file changes:
- sync+restart watches the entire project directory (.) and copies changed files into /code inside the container, then restarts the container. Flask re-reads the updated code on restart.
- rebuild watches requirements.txt specifically. Adding a new dependency requires reinstalling packages, so Compose rebuilds the entire image instead of just syncing a file.
Why sync+restart instead of plain sync?
Flask in debug mode restarts itself when it detects file changes on disk. However, inside a container without a bind mount, Flask cannot detect the sync on its own. The sync+restart action forces a container restart after syncing, which makes Flask start up with the new code already in place.
- Save compose.yaml.
YAML indentation errors?
The develop key must be at the same indentation level as build, ports, and depends_on (4 spaces in from the service name). If you get a parse error when you run the stack, double-check that alignment.
Help me fix a YAML indentation error in my compose.yaml
✔️ Awesome, I've got everything!
Great. Double check you have saved compose.yaml before moving on.
ⓧ 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
develop:
watch:
- action: sync+restart
path: .
target: /code
- action: rebuild
path: requirements.txt
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:
Start the stack with Watch enabled
- Start the stack with file watching enabled by running this command:
docker compose up --watch
What does this command do?
The --watch flag tells Compose to build and start your services as usual, then begin monitoring the paths defined in your develop.watch rules. Any file change that matches a rule triggers the corresponding action automatically.
- Wait for the build to finish and the containers to start. You should see the familiar health check logs, followed by the watch process starting.
- Open http://localhost:8000 in your browser.
- Confirm you see the message: Hello from Docker! I have been seen N time(s). (the counter picks up from where it left off thanks to the named volume).
Page not loading?
Make sure the build completed without errors in your terminal output. If you see a build failure, check that the develop block in compose.yaml is correctly indented under the web service.
Help me debug why my Docker Compose stack is not starting with the --watch flag
Make a live change and verify
Now for the payoff. You will edit your application code, save the file, and watch the change appear in the browser without touching the terminal.
- Open app.py in your text editor.
- Find this line inside the hello() function:
return f"Hello from Docker! I have been seen {count} time(s).\n"
- Change the greeting text to something different, for example:
return f"Hello from Compose Watch! I have been seen {count} time(s).\n"
- Save app.py.
- Switch back to your terminal. Within a few seconds, you should see a message like:
Syncing service "web" after changes were detected
- Refresh http://localhost:8000 in your browser. Your updated greeting appears, and the counter continues incrementing from where it was before.
What just happened?
Compose Watch detected your file save, copied the updated app.py into /code inside the container, and restarted the container. Flask booted up reading the new code. The entire cycle happened in seconds with zero manual intervention.
The counter value persists because Redis data lives on the named volume, which is unaffected by container restarts.
Not seeing the updated text?
Make sure you saved app.py (not just edited it). Check the terminal for the sync message. If you do not see it, confirm that the path: . line in your develop.watch block points to the current directory.
Help me figure out why Compose Watch is not detecting my file changes
You now have a professional development workflow. Edit code, save, and see the result in the browser within seconds. Next up, you will learn how to inspect and debug the running stack using built-in Compose commands.
Inspect and Debug the Running Stack
Your Compose Watch workflow is running smoothly. You can edit code locally and see changes reflected instantly without rebuilding.
Now it's time to peek inside the running stack. Docker Compose provides commands for inspecting resolved configuration, streaming live logs, and executing commands directly inside containers. These are the same tools professional developers use daily to debug containerized applications.
In this step, get ready to:
- Verify that environment variables from your .env file are correctly substituted into the resolved configuration.
- Stream real-time logs and run commands inside running containers.
- Tear down the stack cleanly.
Verify the resolved configuration
Your compose.yaml uses variable interpolation like ${APP_PORT} throughout. The docker compose config command shows you the fully resolved file with all variables replaced by their actual values from .env.
- Stop the watch mode by pressing Ctrl+C in the terminal where docker compose up --watch is running.
- View the fully resolved Compose configuration by running this command:
docker compose config
You should see a YAML output where every ${...} placeholder has been replaced with real values. Look for 8000:5000 in the ports section (from APP_PORT), redis for REDIS_HOST, and 6379 for REDIS_PORT.
Why is this useful?
When your stack behaves unexpectedly, the first debugging step is confirming that Compose resolved your variables correctly. A typo in .env or a missing variable silently breaks port mappings or service connections.
Running docker compose config catches these issues before you waste time digging through container logs.
Seeing unresolved variables in the output?
Make sure your .env file is in the same directory as your compose.yaml. Compose automatically reads .env from the project root.
Check that variable names in .env match exactly what compose.yaml references. They are case-sensitive.
Help me debug my docker compose config output
Stream logs and run commands inside containers
Running containers produce logs, hold environment variables, and maintain state. Docker Compose lets you access all of this directly from your terminal.
- Start the stack in detached mode (runs in the background) so your terminal stays free for inspection commands:
docker compose up -d
You should see both containers start up. The terminal returns to a prompt instead of streaming logs.
- Follow real-time logs from all services by running:
docker compose logs -f
You should see interleaved log output from both the web and redis services. Press Ctrl+C to stop following logs when you're ready to move on.
What does the -f flag do?
The -f flag means "follow." Without it, you get a snapshot of existing logs. With it, new log entries stream to your terminal in real time as they happen.
- Verify that the environment variables are correctly set inside the running web container by running:
docker compose exec web env | grep REDIS
You should see REDIS_HOST=redis and REDIS_PORT=6379 in the output. This confirms that your .env values made it all the way into the container's environment.
- Inspect the live counter value stored in Redis by running:
docker compose exec redis redis-cli GET hits
You should see a number matching how many times you visited the page. This is the same value your Flask app reads when it calls cache.incr("hits").
What does docker compose exec do?
The docker compose exec command runs a command inside an already-running container. You specify the service name and then the command you want to run.
In the first example, you ran env | grep REDIS inside the web container. In the second, you ran redis-cli GET hits inside the redis container to query the database directly.
Seeing "no container found" or "service is not running"?
Make sure you ran docker compose up -d before attempting exec or logs commands. These commands require running containers.
Run docker compose ps to check which containers are currently running. You should see both web and redis listed.
Help me fix docker compose exec errors
Clean up
You've confirmed everything is working. Time to tear down the stack.
- Stop all containers and remove the network by running:
docker compose down
This stops and removes both containers and the default network. Your named volume redis-data is preserved, so your hit counter data still exists on disk.
How do I remove the volume too?
To remove everything including named volumes, use the -v flag:
docker compose down -v
This intentionally resets the hit counter to zero by deleting the persisted Redis data. Use it when you want a completely fresh start.
- Confirm no containers are running by running:
docker compose ps
You should see an empty list with no running containers. Your stack is fully torn down.
Still seeing containers listed?
If containers still appear, try running docker compose down again. A container that was in the middle of shutting down during your first attempt may now be ready to remove.
Help me fully stop my Docker Compose stack
You now have the full toolkit for inspecting containerized applications. You can resolve configurations, stream logs, and reach inside containers to query services directly. These are the same debugging techniques you'd use in production.
Secret mission
Modularize with Multiple Compose Files
Your entire stack lives in a single compose.yaml right now. Real production teams split infrastructure services into separate files so different teams can own different pieces. Use Docker Compose's include directive to modularize your stack across multiple files.
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 on disk, pause them to come back later, or delete them entirely.
Resources you used:
- Docker images built for the web service (stored locally, no ongoing cost).
- Named volume compose-demo_redis-data (stored locally, no ongoing cost).
- Project files in the compose-demo directory on your Desktop.
Keep everything running
No action needed. Choose this if you're still actively building or experimenting with Docker Compose.
- Your images and volumes remain on disk for instant restarts.
- Run docker compose up --watch from the compose-demo directory any time you want to resume.
Pause - I'll come back to this later
Stop running containers to free up memory, but keep images and volumes on disk so you can restart quickly.
- Stop all containers by running:
docker compose down
- Your images and the redis-data volume stay on disk. When you return, run docker compose up --watch to pick up where you left off.
Delete - I don't want to use this again
Remove all containers, volumes, built images, and project files. This is a full reset.
- Remove containers, named volumes, and built images by running:
docker compose down -v --rmi all
- Delete the project folder by running:
cd ~/Desktop
rm -rf compose-demo
- Confirm the folder is gone by running:
ls ~/Desktop
- You should no longer see compose-demo in the output.
Nice Work!
Nice Work!
You just containerized a multi-service application and orchestrated it with Docker Compose. This is the exact workflow production teams use to ship and manage real applications.
You learned how to:
- Containerized a Python Flask application with a Dockerfile and orchestrated it alongside Redis using Docker Compose.
- Implemented health checks and startup ordering to eliminate race conditions, and persisted data across container restarts with named volumes.
- Set up Compose Watch for live development without manual rebuilds, and inspected running containers using docker compose config, docker compose logs, and docker compose exec.
- Secret Mission: Modularized the stack across multiple Compose files using the include directive.
Ready to quiz yourself?