Build and Run a Multi-Container App

Containerize a Flask and Redis app using Docker and Docker Compose.

Introduction

30 Second Summary

Every app you use runs inside a server somewhere. When developers need to move that app to a new machine or scale it for more users, they need a way to package the app and everything it depends on into one portable unit.

In this project, you will use Docker to package a Python web app into a container you build from scratch. You will connect it to a Redis database using Docker Compose and keep your hit count alive across restarts with volumes.

What You'll Build

You will open your browser to localhost:8000 and watch a hit counter climb with every page refresh, with the count surviving even after you tear everything down and restart.

By the end of this project, you'll have:

  • A Flask web app you built and packaged into a Docker container from scratch, ready to run on any machine.
  • Two connected containers sharing a network through Docker Compose, with a Redis database from Docker Hub storing every page visit.
  • Persistent volumes that keep your hit count safe through full container teardowns, and health checks that make sure Redis is ready before your app starts.
  • Secret Mission: Add an Nginx reverse proxy as a third container that sits in front of your Flask app.

Are there any prerequisites?

You will need a GitHub account and basic comfort with the command line. Docker Desktop will be installed in Step 1.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. This step is your chance to commit to the project goal before any tools get installed.

Install Docker Desktop and Run Your First Container

Before you can build or run any containers, you need the Docker engine on your machine. Docker Desktop is the all-in-one package that bundles the Docker engine, a visual dashboard, and the command-line tools you need.

In this step, you will install Docker Desktop, confirm the engine is running, and pull your first container image to see it live in your browser.

In this step, get ready to:
  • Install Docker Desktop for Windows.
  • Verify the Docker engine is running.
  • Pull and run a container from Docker Hub.
Download and install Docker Desktop
  • Open the Docker Desktop for Windows install page in your browser.
  • Click the download button for Docker Desktop for Windows - x86_64 to download the installer.
  • Double-click Docker Desktop Installer.exe to run the installer.
  • The installer asks which installation mode you prefer. Keep the default per-user option selected (this requires no admin rights).
  • On the Configuration page, make sure Use WSL 2 instead of Hyper-V is checked. This is the default.
  • Follow the remaining prompts and select Close when the installation finishes.

What is Docker Desktop?

Docker Desktop is an application that lets you build and run containers on your machine. It includes the Docker engine (the part that actually runs containers), a graphical dashboard, and command-line tools like docker and docker compose.

It uses WSL 2 (Windows Subsystem for Linux) under the hood to run Linux containers on your Windows machine. Docker Desktop is free for personal use, education, and small businesses.

Installation failed or stuck?

Make sure your Windows version is at least Windows 10 version 22H2 or Windows 11 version 23H2. Older versions do not support Docker Desktop with WSL 2.

Check that hardware virtualization is enabled in your BIOS/UEFI settings. Docker needs this to run containers.

help me troubleshoot my Docker Desktop installation on Windows

Start Docker Desktop and confirm it is running

Docker Desktop does not start automatically after installation. You need to launch it manually.

  • Press the Windows key to open your search bar.
  • Type Docker Desktop and press Enter to open it.
  • A Docker Subscription Service Agreement dialog appears on first launch. Select Accept to continue.
  • Wait for the Docker engine to start. The whale icon in your system tray stops animating when the engine is ready.
  • Open a terminal (PowerShell or Command Prompt). Confirm Docker is installed by running this command:
docker --version

Check your output and find the tab that matches what you see.

✔️ I see a version number

Docker is installed and the engine is running. You are ready to run your first container.

ⓧ Command not found

This usually means Docker Desktop has not finished starting, or the installation did not complete.

  • Check the system tray for the Docker whale icon. If it is still animating, wait for it to stop before trying again.
  • If there is no whale icon, open Docker Desktop again from the Start menu. Docker Desktop must be running before you can use docker commands.
  • If Docker Desktop is running but the command still fails, close your terminal and open a new one. The terminal needs to reload its PATH.

help me fix docker --version not being recognized in my terminal

Run your first container

Docker Hub is a public registry where thousands of ready-made container images are stored. You are going to pull an image from Docker Hub and run it as a container on your machine.

  • Pull and run the Docker welcome container by running this command:
docker run -d -p 8080:80 docker/welcome-to-docker

What does this command do?

  • docker run tells Docker to create and start a new container.
  • -d runs the container in the background so your terminal stays free.
  • -p 8080:80 maps port 8080 on your machine to port 80 inside the container. This is called port mapping.
  • docker/welcome-to-docker is the name of the image to pull from Docker Hub.

You should see Docker pull the image layers and then print a long container ID. That means the container is running in the background.

Before you open your browser, what do you expect to see at http://localhost:8080?

You should see the Docker welcome page. This page is being served by the container you just started.

Don't see the welcome page?

  • Make sure you typed the URL as http://localhost:8080 (not https). The container is serving plain HTTP.
  • Check that the docker run command finished without errors. If you see a port conflict message, another application is already using port 8080. Stop that application and try again.

help me troubleshoot why localhost:8080 is not loading the Docker welcome page

Now let's see what containers are running and learn how to stop them.

  • List your running containers by running this command:
docker ps

You should see one row showing the docker/welcome-to-docker container with its container ID, port mapping, and status.

  • Copy the container ID from the first column of the docker ps output.
  • Stop the container by running this command:
docker stop [[CONTAINER_ID="your container ID from docker ps"]]

What is a container ID?

Every container gets a unique ID when it starts. You only need to type the first few characters of the ID. For example, if your container ID is a1b2c3d4e5f6, running docker stop a1b is enough.

  • Confirm the container is stopped by running docker ps again. The list should now be empty.

Docker is installed and you have pulled, run, and stopped your first container. Next up, you will write your own Dockerfile and build a custom image for a Python web app.

Build Your Own Docker Image

In Step 1, you pulled a pre-built image from Docker Hub and ran it. That image was created by someone else.

The real power of Docker is packaging YOUR code into an image that anyone can run. In this step, you'll write a Python web app, define how to containerize it in a Dockerfile, and build your first custom image.

In this step, get ready to:
  • Create a Flask web app and a Dockerfile to containerize it.
  • Build a custom Docker image from your Dockerfile.
  • Run your image and hit a real-world container isolation problem.
Create the project folder and application files

Before writing any Docker configuration, you need the application itself. You'll create a simple Python web app that counts page visits using Flask and Redis.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir hit-counter-app
cd hit-counter-app

You'll create three files in this folder using your text editor. Open your preferred editor (Notepad, VS Code, or any text editor you like).

  • Create a new file called app.py inside the hit-counter-app folder.
  • Paste the following code into app.py:
# Import dependencies
import os
import redis
from flask import Flask

# Create the Flask app and connect to Redis
app = Flask(__name__)
cache = redis.Redis(
    host=os.getenv("REDIS_HOST", "redis"),
    port=int(os.getenv("REDIS_PORT", "6379")),
)

# Define the home page route with a hit counter
@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?

  • cache = redis.Redis(...) creates a connection to a Redis database. It reads the host and port from environment variables, falling back to defaults.
  • cache.incr("hits") increments a counter in Redis every time someone visits the page. Redis stores this count so it persists between requests.
  • The @app.route("/") decorator tells Flask to call hello() whenever someone visits the home page.
  • Save app.py.
  • Create another new file called requirements.txt in the same hit-counter-app folder.
  • Paste these two dependencies into requirements.txt:
flask
redis

What is requirements.txt?

This file lists the Python packages your app depends on. Docker will read it during the build and install both Flask (the web framework) and redis (the Python client for talking to Redis).

  • Save requirements.txt.
  • Confirm both files exist by running this in your terminal:
ls

You should see app.py and requirements.txt listed.

Now you need a Dockerfile. A Dockerfile is a text file with instructions that Docker reads to build an image. Think of it as a recipe that describes exactly what goes into your container.

  • Create a new file called Dockerfile (no file extension) in the hit-counter-app folder.
  • Paste the following into your Dockerfile:
# syntax=docker/dockerfile:1

# Use a lightweight Python image as the base
FROM python:3.12-alpine

# Set the working directory inside the container
WORKDIR /code

# Configure Flask environment variables
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0

# Install system-level build dependencies
RUN apk add --no-cache gcc musl-dev linux-headers

# Copy and install Python dependencies first (for layer caching)
COPY requirements.txt .
RUN pip install -r requirements.txt

# Copy the rest of the application code
COPY . .

# Document the port Flask listens on
EXPOSE 5000

# Start the Flask development server
CMD ["flask", "run", "--debug"]

What does this Dockerfile do?

  • FROM python:3.12-alpine starts from a small Python image. Alpine is a minimal Linux distribution that keeps your image lightweight.
  • WORKDIR /code sets the directory inside the container where your app files will live.
  • COPY requirements.txt . and RUN pip install install your Python dependencies. Copying requirements first means Docker can cache this layer and skip it on rebuilds when only your code changes.
  • CMD ["flask", "run", "--debug"] tells Docker what command to run when the container starts.
  • Save Dockerfile.
  • Confirm all three files are in your project folder by running:
ls

You should see app.py, Dockerfile, and requirements.txt listed.

Missing a file?

  • Make sure you saved each file inside the hit-counter-app folder, not in a parent or nested folder.
  • Check that Dockerfile has no file extension. Some editors add .txt automatically. The file should be named exactly Dockerfile.
  • Make sure your terminal is in the hit-counter-app directory. Run pwd to check your current location.

Still stuck? Help me find my missing project files in the hit-counter-app folder.

✔️ Awesome, I've got everything!

Great. Make sure you've saved all three files before moving on.

ⓧ I'd like to double check the full code

Here's each file. Compare yours against these:

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
redis
# 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"]
Build your Docker image

With your files ready, you can turn them into a Docker image. The docker build command reads your Dockerfile and executes each instruction to create the image.

  • Build your Docker image by running this command:
docker build -t hit-counter .

What does this command do?

  • docker build reads the Dockerfile in the current directory and executes each instruction.
  • -t hit-counter tags (names) the resulting image hit-counter so you can reference it by name later.
  • . tells Docker to use the current folder as the build context, which is the set of files Docker can access during the build.

Watch the terminal output as Docker executes each Dockerfile instruction layer by layer. This may take a minute the first time as it downloads the python:3.12-alpine base image.

  • After the build finishes, verify your image exists by running:
docker images

You should see hit-counter in the list with the tag latest.

Build failed?

  • Make sure your terminal is inside the hit-counter-app folder. The . at the end of the build command refers to the current directory.
  • Check that Dockerfile is spelled exactly right with a capital D and no file extension.
  • Confirm Docker Desktop is running. Look for the whale icon in your system tray.

Still stuck? Help me debug my Docker build failure.

Run your container

Your image is built. Time to run it and see your app in action.

Before you run the next command, think about this: your Flask app tries to connect to a Redis database. Do you think it will succeed? What might go wrong inside an isolated container?

  • Start a container from your image by running:
docker run -p 8000:5000 hit-counter

What does -p 8000:5000 mean?

The -p 8000:5000 flag sets up port mapping. It maps port 8000 on your machine to port 5000 inside the container.

Flask runs on port 5000 inside the container. Port mapping lets you access it through port 8000 on your machine by visiting http://localhost:8000.

This command runs the container in the foreground, so your terminal shows the Flask server output. Leave it running and open a new browser tab.

  • Open a web browser and go to http://localhost:8000.

You'll see an error page instead of the friendly greeting. The app crashed because it tried to connect to a Redis database, and no Redis instance is available.

Why the error?

Your Flask app uses Redis to store the page visit count. But the container is running in complete isolation. There is no Redis server inside it, and no way to reach one outside it.

This is exactly how containers work. Each container is its own isolated environment with its own filesystem, network, and processes. Your app needs a second container running Redis, and a way to connect them.

  • Go back to your terminal and press Ctrl+C to stop the container.

Your app is containerized, but it is stuck running alone with no database to talk to. In the next step, you'll use Docker Compose to spin up both your app and a Redis database as connected containers.

Connect Two Containers with Docker Compose

In the previous step, you built a custom Docker image and ran your Flask app inside a container. But the app crashed because it tried to connect to Redis and there was no Redis container running.

You could fix this by manually starting a Redis container and wiring up networking between the two. But Docker Compose does all of that in a single configuration file. In this step, you will write a compose.yaml that defines both services, then start them together with one command.

In this step, get ready to:
  • Create a compose.yaml file that defines your web app and Redis as connected services.
  • Start both containers with a single command and see the hit counter finally working.
  • Discover why your counter data does not survive a restart.
Create the Compose configuration files

Docker Compose reads a file called compose.yaml to know which containers to start and how they connect. You will define two services in this file: your Flask web app and a Redis database.

Why Docker Compose instead of manual networking?

Without Compose, you would need to create a Docker network manually, start each container individually, and wire them together with --network flags. That works, but it is tedious and error-prone.

Compose captures all of that setup in one file. One command starts everything, creates the network, and connects the containers. This is how real multi-container applications are managed.

  • In your text editor, create a new file called compose.yaml inside your project folder (the same folder that has app.py and your Dockerfile).
  • Paste the following into compose.yaml:
services:
  # Flask web app, built from your Dockerfile
  web:
    build: .
    ports:
      - "${APP_PORT}:5000"
    environment:
      - REDIS_HOST=${REDIS_HOST}
      - REDIS_PORT=${REDIS_PORT}

  # Redis database, pulled from Docker Hub
  redis:
    image: redis:alpine

What does this file do?

  • services: declares the containers that make up your application.
  • web: builds an image from your Dockerfile, maps a host port to the container's port 5000, and passes Redis connection settings as environment variables.
  • redis: pulls the official redis:alpine image from Docker Hub. No build needed since someone else already packaged Redis for you.
  • The ${...} placeholders pull values from a .env file, which you will create next.
  • Save compose.yaml.

The compose.yaml references variables like ${APP_PORT}. Docker Compose reads those from a .env file in the same folder.

  • Create a new file called .env in your project folder and paste the following:
# Port for the web service on localhost
APP_PORT=8000
# Redis connection settings
REDIS_HOST=redis
REDIS_PORT=6379

What do these values mean?

  • APP_PORT=8000 means you will visit your app at http://localhost:8000.
  • REDIS_HOST=redis tells your Flask app to connect to a hostname called redis. Docker Compose automatically creates a network where each service is reachable by its name.
  • Save .env.
  • Confirm both new files exist by running:
ls

You should see compose.yaml and .env alongside your existing app.py, requirements.txt, and Dockerfile.

Don't see both files?

  • Make sure you are in the same folder as app.py. Run pwd to check your current directory.
  • On Windows, .env files can be hidden by default. Try ls -a to show hidden files.

Still stuck? Help me find my compose.yaml and .env files.

✔️ Awesome, I've got everything!

Great. Double check you have saved both compose.yaml and .env.

ⓧ I'd like to double check the full code

Here is the full compose.yaml:

services:
  web:
    build: .
    ports:
      - "${APP_PORT}:5000"
    environment:
      - REDIS_HOST=${REDIS_HOST}
      - REDIS_PORT=${REDIS_PORT}

  redis:
    image: redis:alpine

Here is the full .env:

APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
Start both containers with Docker Compose

Your configuration is ready. One command will build your web image, pull the Redis image, create a shared network, and start both containers.

  • Start both services by running this command:
docker compose up

What is happening in your terminal?

Docker Compose executes several steps in sequence. It builds your web image from the Dockerfile, pulls the redis:alpine image from Docker Hub, and creates a network so the two containers can talk to each other.

This command runs in the foreground, so your terminal will display live logs from both services. You will need to open a new terminal window later to run other commands.

In the last step, visiting http://localhost:8000 showed a Redis connection error. Do you think it will work this time?

  • 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. Watch the counter go up with each refresh.

Your Flask app is now connected to Redis through the Docker Compose network. Every refresh calls cache.incr("hits") in app.py, and Redis keeps the running total.

Still seeing a connection error?

  • Check that your .env file has REDIS_HOST=redis (not localhost). The hostname redis matches the service name in compose.yaml.
  • Make sure both compose.yaml and .env are saved in the same folder as your Dockerfile.
  • If port 8000 is already in use, stop any previous containers with docker compose down and try again.

Need more help? Help me debug my Docker Compose setup.

Discover the persistence problem

Your counter works, but how sturdy is it? Docker Compose gives you commands to inspect and manage your running services. You will use them now to explore what happens when you stop and restart everything.

  • Open a new terminal window. Your first terminal is busy showing live logs from docker compose up.
  • Navigate to your project folder in the new terminal.
  • Check which services are running by running:
docker compose ps

You should see two rows: one for the web service and one for the redis service, both showing a running status.

Now comes the real test. Refresh your browser a few more times to build the counter up to a memorable number. Then stop and remove everything.

  • Stop and remove both containers by running:
docker compose down

What does docker compose down do?

This command stops all running containers defined in your compose.yaml and removes them along with the network Compose created. The containers are gone, not just stopped.

Before you start the containers again, make a prediction. Will your hit counter pick up from where you left off, or will it reset to 1?

  • Start everything back up by running:
docker compose up
  • Open your browser and go to http://localhost:8000.

The counter is back to 1. All your previous visits are gone.

Why did the counter reset?

Redis was storing the hit count inside the container's memory. When docker compose down removed the container, that data was destroyed with it.

This is a fundamental property of containers. They are disposable by design. Any data stored inside a container disappears when the container is removed. This is the next problem to solve.

Your two containers are talking to each other and the hit counter works. But your data is living on borrowed time inside the container. In the next step, you will add persistent storage so the counter survives restarts.

Persist Data with Volumes and Add Health Checks

In the last step, you got two containers talking to each other and watched the hit counter climb with every refresh. But you also discovered a problem. After running docker compose down and starting everything back up, the counter reset to 1.

That happened because Redis stores its data inside the container. When the container is removed, the data disappears with it. Docker volumes solve this by storing data on your host machine, outside the container lifecycle. You'll also add health checks to make sure Redis is fully ready before your web app tries to connect.

In this step, get ready to:
  • Add a named volume to persist Redis data across container restarts.
  • Add a health check so the web service waits for Redis to be ready.
  • Verify that your hit counter survives a full restart.
Create a .dockerignore file

Before updating your Compose configuration, there is one housekeeping file to add. A .dockerignore file tells Docker which files to exclude from the build context when it builds your image.

  • Create a new file called .dockerignore in your project folder (the same folder that has your Dockerfile) and add the following contents:
.env
*.pyc
__pycache__
redis-data

What does this file do?

This keeps your .env file (which may contain secrets) out of your Docker image. It also excludes Python bytecode files (*.pyc, __pycache__) and the redis-data folder. Less noise in the build context means faster builds.

  • Save .dockerignore.
  • Confirm the file exists by running:
ls -a

You should see .dockerignore listed alongside your other project files.

Add volumes and health checks to your Compose file

Right now your compose.yaml runs two services, but Redis has no persistent storage and no health check. You need to add three things: a volume so Redis data survives restarts, a health check so Docker knows when Redis is ready, and a startup dependency so the web service waits.

  • Open compose.yaml in your text editor and replace its entire contents with the following:
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:

What changed in this file?

  • volumes: - redis-data:/data mounts a named volume called redis-data to Redis's /data directory. This stores Redis data on your host machine instead of inside the container.
  • healthcheck runs redis-cli ping every 5 seconds to confirm Redis is accepting connections. If the ping fails 5 times in a row, Docker marks the container as unhealthy.
  • depends_on with condition: service_healthy tells Docker Compose to wait until Redis passes its health check before starting the web service.
  • The top-level volumes: block at the bottom declares redis-data as a named volume that Docker manages for you.
  • 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

Here is the complete compose.yaml file. Make sure yours matches exactly.

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:
  • Start your services with the updated configuration by running:
docker compose up

Watch the terminal output. You should see Docker wait for Redis to become healthy before starting the web service. Look for the word Healthy in the output for the Redis container.

Seeing a YAML error?

  • YAML is sensitive to indentation. Make sure you are using spaces, not tabs. Each nested level uses exactly 2 spaces.
  • Check that the healthcheck block is indented under redis: at the same level as image:.
  • Make sure the top-level volumes: block is at the very bottom of the file with no indentation, at the same level as services:.

Need more help? Ask: Why is Docker Compose giving me a YAML parsing error on my compose.yaml?

Build up a hit count and test persistence

Your containers are running with the new volume and health check. Time to prove that the counter actually persists across restarts.

  • Open http://localhost:8000 in your browser.
  • Refresh the page 4 or 5 times. Each refresh increments the counter.
  • Note the number you see. You'll check it again after a full restart.

Now stop and remove everything. Open a new terminal window and run:

  • Stop and remove your containers by running:
docker compose down

Before you start everything back up, think about what you expect to happen. Will the counter pick up where it left off, or will it reset to 1?

  • Bring your services back up by running:
docker compose up
  • Open http://localhost:8000 in your browser.

The counter continues from where it left off. Your named volume stored the Redis data on your host machine, so it survived the container being removed and recreated.

What is a Docker volume?

A volume is a piece of storage that lives outside any container. Containers come and go, but volumes stick around until you explicitly delete them. The redis-data:/data mapping tells Docker to connect the named volume redis-data to the /data directory inside the Redis container.

When Redis writes data, it goes to /data inside the container, which is actually stored in the volume on your host. New containers mount the same volume and pick up right where the old one left off.

Sometimes you do want a fresh start. The -v flag removes named volumes along with your containers.

  • Stop your containers and delete the volume by running:
docker compose down -v
  • Start everything again by running:
docker compose up
  • Open http://localhost:8000 in your browser.

This time the counter is back to 1. The -v flag permanently deleted the volume and all the data it held.

Counter still showing the old number?

  • Make sure you ran docker compose down -v with the -v flag. Without it, the volume is preserved.
  • Try a hard refresh in your browser (Ctrl+Shift+R on Windows) to clear any cached response.

Still stuck? Ask: Why does my Docker hit counter not reset after docker compose down -v?

You now have docker compose ps available to check all running services at any time. Run it in a new terminal to see both containers listed with their status and ports.

Secret mission

Add an Nginx Reverse Proxy

In production, you never expose your application server directly to the internet. A reverse proxy like Nginx sits in front of your app, handling incoming traffic and forwarding it to the right place. In this mission, add Nginx as a third container to create a multi-tier architecture.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on your local machine, so there are no ongoing cloud costs. Docker Desktop does use some system resources (RAM, CPU) while running in the background.

Decide whether to keep your containers running, pause them to come back later, or delete them entirely.

Resources you used:

  • Docker containers (nginx, web, redis) running via Docker Compose.
  • Docker images built locally (hit-counter and nginx reverse proxy).
  • Docker volume (redis-data) storing persistent data on your local disk.
  • Project folder on your Desktop containing all project files (app.py, compose.yaml, Dockerfile, and others).

Keep everything running

No action needed. Choose this if you are still actively building or experimenting with your containers.

  • Your three containers (nginx, web, redis) continue to run in the background.
  • Docker Desktop uses some system RAM and CPU while running. You can check resource usage from the Docker Desktop dashboard.
  • Your hit counter data persists in the redis-data volume, so your count picks up right where you left off.

Pause - I'll come back to this later

Shut down your running containers to free up system resources. Your data, images, and containers are all preserved so you can restart later.

  • Open your terminal and navigate to your project folder (the one containing compose.yaml).
  • Stop all containers without removing them by running:
docker compose stop
  • When you are ready to come back, navigate to your project folder and run:
docker compose up
  • Your containers restart with all your data intact.

Delete - I don't want to use this again

Remove all project resources and start fresh. This permanently deletes your containers, volumes, and stored data.

  • Open your terminal and navigate to your project folder (the one containing compose.yaml).
  • Stop and remove all containers, networks, and volumes by running:
docker compose down -v
  • The -v flag removes named volumes (including redis-data), so your hit counter data is permanently deleted.
  • Clean up unused Docker images by running:
docker image prune
  • For a full cleanup of all unused Docker objects (images, build cache, and networks), run:
docker system prune

Both prune commands will ask you to confirm. Type y and press Enter to proceed.

  • Delete your project folder from the Desktop. Right-click the folder in File Explorer (Windows) or Finder (macOS) and select Delete.

Nice Work!

Nice Work!

You just built and ran a multi-container application from scratch using Docker. A Flask web app, a Redis database, and an Nginx reverse proxy, all wired together and running on your machine.

You've learned how to:

  • Write a Dockerfile from scratch, build a custom Docker image, and run it as a container.
  • Connect multiple containers with Docker Compose, experiencing firsthand why an isolated container needs a shared network and service orchestration to function.
  • Add health checks to control service startup order and named volumes to persist data across container restarts.
  • Secret Mission: Add an Nginx reverse proxy as a third container, creating a multi-tier architecture that mirrors real production deployments.

Ready to quiz yourself?