Build a Reliable Docker Compose Counter
Build a Redis visit counter with Docker Compose health checks and volumes.
Introduction
30 Second Summary
A number on a screen can look dependable until a restart sends it back to zero. That small reset shows how quickly useful information can disappear.
In this project, you will build a browser-based visit counter as a two-service Docker Compose application. You will expose its weak points before making the count survive container replacement.
What You'll Build
You will refresh a live page after replacing its containers to see the visit count continue from its saved value.
By the end of this project, you'll have:
- A live visit counter that increases every time you refresh the browser.
- A reliable two-service application where Flask serves the page while Redis stores the shared count.
- A terminal inspection workflow that reveals the stack's configuration, health status, request logs, and stored count.
- An optional Secret Mission: add a dependency-aware /health endpoint that reports whether the application can reach Redis.
Are there any prerequisites?
This project assumes a Windows computer with Docker Desktop and Visual Studio Code installed.
Basic confidence editing small Python files is enough. Local Python, Redis, Azure resources, and a separate WSL terminal workflow are not required.
Before We Start
This is your moment to commit to a browser-based visit counter whose value survives container replacement. That goal gives every reliability improvement a visible purpose.
Verify Your Container Toolchain
Correct application files can look broken when the Docker Engine is unreachable. Checking the platform first keeps later troubleshooting focused on your project.
You’ll start Docker Desktop in Linux-container mode. The remaining checks prepare one Visual Studio Code workspace for every Compose command that follows.
In this step, get ready to:
- Verify the container toolchain through Docker Compose.
- Prepare the docker-counter workspace inside Visual Studio Code.
- Launch Microsoft Edge for the browser tests ahead.
Start Docker Desktop
Docker Desktop provides the local engine that creates your Linux containers. Its first launch can take a moment while that engine starts.
- Press the Windows key to open Windows Search.
- Type Docker Desktop into the search field.
- Press Enter to start Docker Desktop.
Wait for Docker Desktop to finish starting. You’ll verify that its engine is reachable after your workspace is ready.
Create the project workspace
A Visual Studio Code workspace keeps your files beside a terminal that already points at the correct folder. This prevents future Compose commands from running against the wrong location.
- Press Windows+E to open File Explorer.
- Select Desktop in the left navigation pane.
- Press Ctrl+Shift+N to create a new folder.
- Type docker-counter as the folder name.
- Press Enter to save the folder name.
You should see an empty docker-counter folder on your Desktop.
- Press the Windows key to open Windows Search.
- Type Visual Studio Code into the search field.
- Press Enter to open Visual Studio Code.
Why open a workspace?
Visual Studio Code treats an opened folder as a workspace. The Explorer sidebar displays that folder while the integrated terminal starts inside it.
About Workspace Trust
Visual Studio Code may show a Workspace Trust prompt when the folder opens. You created this empty folder yourself, so you can trust its contents.
- Select File in the top menu bar.
- Select Open Folder....
- Select Desktop in the folder picker.
- Select the docker-counter folder.
- Select Select Folder.
- Select Yes, I trust the authors if the Workspace Trust prompt appears.
- Select Terminal in the top menu bar.
- Select New Terminal.
You should see docker-counter in the Explorer sidebar. The integrated terminal prompt should point to that folder.
Launch Edge and verify the toolchain
Edge displays the counter later in the project. The terminal checks now confirm that Compose can reach a Linux Docker Engine before you create any application files.
- Press the Windows key to open Windows Search.
- Type Microsoft Edge into the search field.
- Press Enter to launch Microsoft Edge.
✔️ Microsoft Edge opens
Microsoft Edge is ready to display your counter when the first container starts serving requests.
ⓧ Microsoft Edge is unavailable
Install Edge from Microsoft’s official download page before continuing.
- Open the Download Microsoft Edge page.
- Download the Windows installer from the page.
- Run the downloaded installer.
- Approve the Windows permission prompt if it appears.
- Press the Windows key after installation finishes.
- Type Microsoft Edge into the search field.
- Press Enter to confirm that Microsoft Edge launches.
Edge still unavailable?
Confirm that the installer finished before searching again. A managed Windows device may require permission from its administrator.
Help me troubleshoot the Edge installation.
A browser window confirms that Edge is available. The final checks now happen in your prepared workspace.
Before you run these checks, do you expect the terminal to reach both the Docker client and its server?
- Return to the Visual Studio Code integrated terminal from earlier.
- Verify the Docker Engine connection by running this command:
docker version
What does this check?
The output separates the command-line client from the engine server. A working connection shows both Client information and Server information.
- Verify Docker Compose by running this command:
docker compose version
What does this check?
This asks the Compose CLI for its version information. A printed version confirms that Docker Desktop supplied the Compose command your project needs.
✔️ I see Client, Server, and Compose output
That’s the platform check complete. Your Docker client can reach its server, the server reports Linux architecture, and Compose is ready inside your workspace.
ⓧ The Server section is missing
A missing Server section means the client cannot reach the Docker Engine. A Docker Desktop warning about WSL points to the Windows subsystem that supports the Linux engine.
- Press the Windows key to open Windows Search.
- Type PowerShell into the search field.
- Open an Administrator PowerShell terminal from the search result.
- Check the installed WSL version by running:
wsl --version
What should I see?
This prints the installed WSL version when WSL is present. Docker Desktop’s WSL backend requires version 2.1.5 or later.
- Update WSL when the installed version is below 2.1.5 by running:
wsl --update
What does this command do?
This updates the WSL installation used by Docker Desktop’s Linux-container backend. Run it only when the version check shows that an update is required.
- Install WSL when the version command is unavailable by running:
wsl --install
What does this command do?
This installs WSL on a supported Windows system. Run it only when WSL is absent.
- Restart Windows after the update or installation completes.
- Start Docker Desktop again through Windows Search.
- Return to the Visual Studio Code integrated terminal.
- Repeat both toolchain checks by running:
docker version
docker compose version
What should change?
The Docker output should now include its Server section. The Compose command should print version information beneath it.
Still missing the Server section?
Confirm that Docker Desktop finished starting after the Windows restart. Check Docker Desktop for another WSL warning before repeating the commands.
Help me restore the Docker Engine connection.
ⓧ The server is using Windows containers
Your project uses official Linux images. Docker Desktop needs Linux-container mode before those images can run.
- Open the Docker Desktop menu from the Windows system tray.
- Select Switch to Linux containers.
- Wait for Docker Desktop to finish switching modes.
- Return to the Visual Studio Code integrated terminal.
- Repeat the engine check by running:
docker version
What should change?
The Server section should now identify a Linux architecture. This confirms that Docker Desktop is ready for the project’s Python and Redis images.
Linux mode still unavailable?
Finish any Docker Desktop restart already in progress. Recheck the menu after the engine reports that it is running.
Help me switch Docker Desktop to Linux containers.
Your Windows container toolchain is ready. Next up, you’ll turn your own Flask code into an image and serve the first visible counter.
Build the First Counter
Your Windows Docker toolchain is ready. This step turns the empty workspace into a running visit counter.
You will package a small Flask app as a container image. Refreshing the page will prove that your code is running inside a container.
In this step, get ready to:
- Write a Flask application that counts visits in memory.
- Package the application in a locally built Docker image.
- Run the counter with Docker Compose in Microsoft Edge.
Write the counter application
The counter needs one variable that survives between requests while the web process keeps running. The module-level visits variable provides that temporary in-memory state.
- Use Visual Studio Code's file sidebar to create app.py inside the open docker-counter folder with this code:
from flask import Flask
app = Flask(__name__)
visits = 0
@app.route("/")
def counter():
global visits
visits += 1
return f"""
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Visit Counter</title>
</head>
<body>
<main>
<h1>Visit Counter</h1>
<p>This page has been visited <strong>{visits}</strong> time(s).</p>
</main>
</body>
</html>
"""
What does this code do?
- The Flask(__name__) call creates the web application.
- The visits variable stores the current count inside the running Python process.
- The @app.route("/") decorator sends homepage requests to counter().
- The counter() function increases the count once per request.
- The returned HTML places the latest count inside the page.
- Save app.py in Visual Studio Code.
You should see app.py in the file sidebar.
Does app.py look incomplete?
- Confirm that app.py sits directly inside docker-counter.
- Compare the indentation inside counter() with the code block above.
- Check that the opening triple quotes have matching closing triple quotes.
Help me check my counter code.
Flask runs inside the image, so the build needs an exact dependency list. The requirements file gives the image a repeatable package version.
- Use Visual Studio Code's file sidebar to create requirements.txt inside docker-counter with this content:
Flask==3.1.3
What does this dependency do?
The Flask==3.1.3 entry tells the image build to install the exact Flask release used by this project. The fixed version keeps future builds consistent.
- Save requirements.txt in Visual Studio Code.
You should now see app.py plus requirements.txt in the file sidebar.
Missing the requirements file?
- Check that the filename ends with .txt.
- Confirm that the file contains the version pin on one line.
Help me check my requirements file.
Package the application image
A Dockerfile turns the application files into an image. Its instructions choose the Python environment that runs inside every new web container.
- Use Visual Studio Code's file sidebar to create Dockerfile inside docker-counter with this build recipe:
# syntax=docker/dockerfile:1
FROM python:3.14.8-slim-trixie
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
How does this image run the app?
- The python:3.14.8-slim-trixie base image supplies Python inside the container.
- The WORKDIR instruction gives the application a consistent directory.
- The first COPY instruction adds the dependency list before installation.
- The second COPY instruction adds the application code.
- The FLASK_APP=app.py value identifies the Flask application file.
- The FLASK_RUN_HOST=0.0.0.0 value makes Flask reachable through the container network.
- The image exposes container port 5000.
- The final CMD starts Flask whenever the container starts.
- Save Dockerfile in Visual Studio Code.
You should see Dockerfile beside the two application files in the file sidebar.
Does the Dockerfile look different?
- Confirm that the filename is exactly Dockerfile.
- Check that Visual Studio Code did not add a file extension.
- Compare the instruction order with the code block above.
Help me check my Dockerfile.
A build context contains the files Docker can send into an image build. Excluding Python cache files keeps that context focused on the source files the image needs.
- Use Visual Studio Code's file sidebar to create .dockerignore inside docker-counter with these patterns:
*.pyc
__pycache__
What gets excluded?
- The *.pyc pattern excludes compiled Python files.
- The __pycache__ pattern excludes Python cache directories.
- Save .dockerignore in Visual Studio Code.
You should now see four project files in the file sidebar.
Can't see .dockerignore?
- Confirm that the filename starts with a period.
- Check that the filename has no extra extension.
Help me find my Docker ignore file.
Run the counter with Docker Compose
Docker Compose reads compose.yaml as the model for your application. This first model contains one service named web.
The port mapping connects port 8000 on Windows to Flask on port 5000 inside the container.
- Use Visual Studio Code's file sidebar to create compose.yaml inside docker-counter with this service definition:
services:
web:
build: .
ports:
- "8000:5000"
What does this configuration do?
- The web service represents the counter application.
- The build: . setting builds the service from the Dockerfile in docker-counter.
- The "8000:5000" mapping makes the application available through Windows port 8000.
- Save compose.yaml in Visual Studio Code.
Your file sidebar should now list app.py, requirements.txt, Dockerfile, .dockerignore, plus compose.yaml.
✔️ Awesome, I've got everything!
Your five project files are saved. You are ready to build the image.
ⓧ I'd like to double check the full code
Compare each saved file with these complete versions.
from flask import Flask
app = Flask(__name__)
visits = 0
@app.route("/")
def counter():
global visits
visits += 1
return f"""
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Visit Counter</title>
</head>
<body>
<main>
<h1>Visit Counter</h1>
<p>This page has been visited <strong>{visits}</strong> time(s).</p>
</main>
</body>
</html>
"""
Flask==3.1.3
# syntax=docker/dockerfile:1
FROM python:3.14.8-slim-trixie
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
*.pyc
__pycache__
services:
web:
build: .
ports:
- "8000:5000"
Before you start the stack, predict whether the browser reaches the counter on the first attempt.
- Build the image and start the web service by running this command in the integrated terminal:
docker compose up
What does this command do?
Docker Compose builds the local web image from Dockerfile. It starts the resulting container in the foreground.
The terminal remains attached so you can read the Flask logs. Keep this command running while you test the page.
You should see build progress followed by logs from the running web service.
- Switch back to Microsoft Edge.
- Enter http://localhost:8000 in the address bar.
You should see the Visit Counter heading. The first displayed count should be 1.
Before you refresh, predict the number that should appear next.
- Refresh the page several times.
The displayed number should increase by one after every refresh. You have your first visible win: the counter is responding from inside the web container.
Can't reach the counter?
- Confirm that the integrated terminal still shows the running Compose process.
- Check that compose.yaml maps host port 8000 to container port 5000.
- Check that no other local application is using port 8000.
Help me troubleshoot the counter page.
- Return to the integrated terminal.
- Press Ctrl+C to stop the foreground Compose run.
The web container stops. Your five project files remain in the docker-counter workspace.
Your first containerized counter works. Next, you will move its temporary count into a separate Redis service.
Move State into Redis
Your first Docker Compose stack already turns Flask code into a page you can refresh. Its visit count still belongs to one web process.
A separate Redis service gives the application a shared place for state. You will test how that state behaves when its container is replaced.
In this step, get ready to:
- Add Redis as a second Compose service.
- Move the visit count from process memory into Redis.
- Replace the stack to test whether Redis keeps the count.
Connect the Compose services
Compose gives each service a hostname matching its service name. The web service can therefore reach Redis at redis without an IP address.
- In Visual Studio Code, select compose.yaml from the file sidebar.
- Replace the entire contents of compose.yaml with this configuration:
services:
web:
build: .
ports:
- "8000:5000"
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
redis:
image: redis:8.10.2-alpine3.23
What does this configuration do?
- The environment section gives the web container the Redis hostname.
- The REDIS_PORT=6379 value identifies the Redis port.
- The redis service runs the pinned redis:8.10.2-alpine3.23 image.
- Save compose.yaml.
- Switch back to the Visual Studio Code integrated terminal from earlier.
- Start both services by running this command:
docker compose up
What does this command do?
Docker Compose reads the updated application model. It starts the existing web service beside the new Redis service.
- Return to the Microsoft Edge tab from earlier.
- Refresh http://localhost:8000.
You should still see the original in-memory visit counter increase. This confirms that both services can run together before the application connects to Redis.
- Switch back to the integrated terminal.
- Press Ctrl+C to stop the foreground stack.
Did one of the services fail to start?
Confirm that Docker Desktop is still running. Check that the indentation in compose.yaml matches the configuration above.
A first image download can also fail when the network connection drops. Run the command again after the connection is stable.
Help me troubleshoot the two-service stack.
Move the counter into Redis
The application needs the redis-py client to send commands from Python to Redis. Adding the client to the container image prepares the web service for the new counter logic.
- Select requirements.txt from the file sidebar.
- Replace the entire contents of requirements.txt with these dependencies:
Flask==3.1.3
redis==8.1.0
What did you add?
The redis==8.1.0 dependency provides the Python client used by app.py. The existing Flask dependency continues to serve the page.
- Save requirements.txt.
The rebuild can take a few minutes while Docker installs the new dependency. The terminal continues printing build progress during this work.
- Rebuild the web image with the new dependency by running this command:
docker compose up --build
What does this command do?
The build installs every package listed in requirements.txt inside the web image. Docker Compose then starts the web service beside Redis.
You should see the image build complete before both services start. The browser still shows the original counter because its Python logic has not changed yet.
- Switch back to the integrated terminal.
- Press Ctrl+C to stop the foreground stack.
Did the image rebuild fail?
Check that requirements.txt contains both dependency lines exactly. A network interruption can also stop the package download.
Help me fix the Redis client build.
The Python code now needs a Redis connection. Environment variables keep the hostname and port aligned with compose.yaml.
- Select app.py from the file sidebar.
- Replace the entire contents of app.py with this connection setup:
import os
from flask import Flask
from redis import Redis
app = Flask(__name__)
cache = Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
)
How does the connection work?
- The os.getenv() calls read the connection values supplied by Compose.
- The Redis client stores that connection as cache.
- The default values match the Redis service name and port from compose.yaml.
- Add this route below the Redis connection setup:
@app.route("/")
def counter():
count = cache.incr("hits")
return f"""
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reliable Visit Counter</title>
</head>
<body>
<main>
<h1>Reliable Visit Counter</h1>
<p>This stack has served <strong>{count}</strong> visit(s).</p>
<p>Web: Flask | State: Redis | Orchestration: Docker Compose</p>
</main>
</body>
</html>
"""
What does the new route do?
- The cache.incr("hits") call increments the Redis key named hits.
- Redis begins at zero when the key does not exist. The first increment therefore returns 1.
- The page displays the returned value as the visit count.
- Save app.py.
Before you start the rebuilt application, do you expect the new counter to continue the old in-memory value or begin with a fresh Redis value?
- Build the updated application and start both services by running this command:
docker compose up --build
What does this run prove?
Docker copies the updated app.py into the web image. The running application now sends every counter increment to the Redis service.
- Return to the Microsoft Edge tab from earlier.
- Refresh http://localhost:8000.
You should see the heading Reliable Visit Counter with a Redis-backed count of 1.
- Refresh the page once more.
You should see the count increase to 2. Your web container is now updating state in a separate Redis container.
Does the counter page fail to load?
Check that the web service uses REDIS_HOST=redis. Confirm that the Compose service itself is also named redis.
Compare the indentation in compose.yaml with the full-code check below. YAML indentation controls which settings belong to each service.
Help me debug the Redis-backed counter.
✔️ Awesome, I've got everything!
Great work. Save app.py, requirements.txt, and compose.yaml before testing container replacement.
ⓧ I'd like to double check the full code
Compare all five files in your docker-counter workspace with these versions.
*.pyc
__pycache__
# syntax=docker/dockerfile:1
FROM python:3.14.8-slim-trixie
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
EXPOSE 5000
CMD ["flask", "run"]
import os
from flask import Flask
from redis import Redis
app = Flask(__name__)
cache = Redis(
host=os.getenv("REDIS_HOST", "redis"),
port=int(os.getenv("REDIS_PORT", "6379")),
)
@app.route("/")
def counter():
count = cache.incr("hits")
return f"""
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Reliable Visit Counter</title>
</head>
<body>
<main>
<h1>Reliable Visit Counter</h1>
<p>This stack has served <strong>{count}</strong> visit(s).</p>
<p>Web: Flask | State: Redis | Orchestration: Docker Compose</p>
</main>
</body>
</html>
"""
services:
web:
build: .
ports:
- "8000:5000"
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
redis:
image: redis:8.10.2-alpine3.23
Flask==3.1.3
redis==8.1.0
Replace the stack and inspect the count
The counter now works through Redis. Replacing the stack reveals where Redis is storing that value.
- Refresh the counter page until the displayed count reaches at least 3.
- Switch back to the integrated terminal.
- Press Ctrl+C to stop the foreground stack.
- Remove the service containers and Compose network by running this command:
docker compose down
What did this command remove?
Docker Compose removes the web container and the Redis container. It also removes the default network created for the application.
Before you recreate the stack, do you expect the next visit to remain above 3 or begin from a fresh value?
- Recreate the two-service stack by running this command:
docker compose up
What happens during recreation?
Docker Compose creates a new Redis container from the configured image. The new container has its own writable storage layer.
- Return to the counter page in Microsoft Edge.
- Refresh http://localhost:8000 once.
You should see the first visit return to 1. This reset is the intended result because the removed Redis container owned the old hits value.
- Switch back to the integrated terminal.
- Press Ctrl+C to stop the foreground stack.
Did the count stay above one?
Confirm that the teardown command completed before you started the stack again. The terminal should no longer show the previous containers as running.
Check that compose.yaml matches the full-code version above. This step must not include a volume for Redis.
Help me reproduce the Redis reset.
You have separated the web process from its state and exposed the storage gap caused by container replacement. Next, you will protect startup readiness and preserve the counter across a full stack recreation.
Add Health Checks and Persistence
Your two-service counter now stores its hits in Redis. The previous recreation proved that container replacement erases them.
Docker Compose currently knows only that Redis has started. A health check makes Redis prove that it can answer requests.
A named volume gives Redis a storage location that survives container replacement. Together, these changes protect startup readiness plus the counter value.
In this step, get ready to:
- Add a Redis health check that tests readiness.
- Make the web service wait for healthy Redis.
- Preserve the counter across stack recreation.
Add a Redis health check
A running Redis container can still need time before it can serve data. The health check creates a readiness signal that Compose can monitor.
- In the Visual Studio Code Explorer sidebar, select compose.yaml.
- Find the image: redis:8.10.2-alpine3.23 line under the redis service.
- Add this health check below the image line:
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
What does the health check do?
- The test command uses redis-cli ping to check whether Redis can answer a request.
- The interval controls how often Compose runs the check.
- The retries value gives Redis several chances to become healthy.
- The start_period value gives Redis time to start before failed checks count against it.
- Save compose.yaml.
- Start the stack with its new Redis health check by running:
docker compose up
What should you see?
Compose starts both services. Redis reaches a healthy state after it begins answering the configured checks.
- Press Ctrl+C in the integrated terminal to stop the foreground stack.
Redis never becomes healthy?
Check that healthcheck aligns with image inside the redis service. Confirm that every setting beneath healthcheck uses the same indentation.
Make sure Docker Desktop remains in Linux-container mode.
Help me troubleshoot the Redis health check.
The health signal now exists. The web service still needs an explicit rule that connects its startup to that signal.
- Find the environment block under the web service.
- Add this dependency block below the environment variables:
depends_on:
redis:
condition: service_healthy
How does the dependency work?
- The depends_on block declares that the web service depends on Redis.
- The service_healthy condition holds the web service until the Redis health check passes.
- Save compose.yaml.
A short pause before the web service starts is expected because Redis receives a 10s start period.
Before you start the stack, do you expect the web service to start before Redis can answer its health check?
- Test the dependency condition by running:
docker compose up
What changed at startup?
Redis passes its health check before the web service starts. Compose now coordinates readiness instead of relying only on startup order.
- Press Ctrl+C in the integrated terminal to stop the foreground stack.
Web service starts incorrectly?
Check that depends_on aligns with environment under the web service. Confirm that condition: service_healthy sits beneath the nested redis entry.
Help me troubleshoot the startup dependency.
Persist the counter in a named volume
The readiness rule protects startup timing. Redis still needs durable storage for the hits value.
The official Redis image stores persistent data in /data. Mounting a named volume there separates the counter from the replaceable container.
- Find the redis service near the bottom of compose.yaml.
- Replace the current redis service block through the end of the file with this configuration:
redis:
image: redis:8.10.2-alpine3.23
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
volumes:
redis-data:
How does the volume preserve data?
- The redis-data:/data mount sends Redis data into the named volume.
- The top-level volumes block declares redis-data as part of the Compose application.
- Compose can replace the Redis container while keeping the volume available.
- Save compose.yaml.
✔️ Awesome, I've got everything!
Your compose.yaml now coordinates Redis readiness plus persistent storage. Double-check that you saved the file.
ⓧ I'd like to double check the full code
Compare your complete compose.yaml with this reference:
services:
web:
build: .
ports:
- "8000:5000"
environment:
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
redis:
condition: service_healthy
redis:
image: redis:8.10.2-alpine3.23
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
start_period: 10s
volumes:
redis-data:
Recreate the stack and prove persistence
A persistent counter needs to survive the same recreation sequence that reset it earlier. This final test replaces the containers while leaving redis-data intact.
- Start the updated stack by running:
docker compose up
What should happen first?
Compose creates or attaches the named volume. Redis becomes healthy before the web service starts.
Stack does not start?
Check the indentation around the volumes blocks. The mount belongs inside the redis service while the declaration begins at the left edge of compose.yaml.
Help me fix the Compose configuration.
- Return to Microsoft Edge from earlier.
- Enter http://localhost:8000 in the address bar.
- Refresh the page until the displayed count reaches at least 3.
- Record the displayed number as your counter value before recreation.
- Return to the integrated terminal from earlier.
- Press Ctrl+C to stop the foreground stack.
- Remove the service containers plus the Compose network by running:
docker compose down
What does this teardown preserve?
Compose removes the service containers plus the default network. The redis-data named volume remains because this command does not remove volumes.
Before you restart the stack, do you expect the next visit to reset to 1 or continue above your counter value before recreation?
- Recreate the stack by running:
docker compose up
What proves the fix worked?
Redis becomes healthy before the web service starts. The recreated Redis container attaches to the existing redis-data volume.
- Return to Microsoft Edge from earlier.
- Refresh http://localhost:8000 once.
You'll see a count above your counter value before recreation. The counter now survives stack recreation.
- Return to the integrated terminal from earlier.
- Press Ctrl+C to stop the foreground stack.
Did the counter reset?
Confirm that the Redis service mounts redis-data:/data. Check that the top-level volumes declaration remains in compose.yaml.
Help me troubleshoot the reset counter.
That closes both reliability gaps. Redis now proves readiness before the web service starts.
Your counter also survives container replacement. Next up, you'll inspect the running stack from the terminal.
Inspect the Running Stack
Your counter now survives stack recreation because Redis stores its value in a named volume. Next, you'll inspect the running stack from the terminal.
A reliable service needs operational evidence. Docker Compose exposes the applied configuration through its rendered model.
Its commands also reveal container health. You can trace a browser request through the logs before reading the live counter directly from Redis.
In this step, get ready to:
- Confirm the applied Compose model.
- Observe the running services from the terminal.
- Compare the live Redis value with the browser count.
Inspect the rendered Compose model
The contents of compose.yaml describe your intended stack. The rendered model shows the configuration that Compose actually applies.
- Switch back to the Visual Studio Code integrated terminal from earlier.
- Render the applied Compose model by running this command:
docker compose config
What does this command show?
- Compose parses compose.yaml before printing its resolved configuration.
- The output contains the web service with host port 8000 connected to container port 5000.
- The output contains the redis service with its health check.
- The model includes the service_healthy dependency condition plus the redis-data volume mounted at /data.
You have now confirmed that Compose resolves both services with their reliability settings intact. The rendered model is your evidence of the configuration Docker applies.
Missing a service or reliability setting?
- Check that the terminal path ends with docker-counter.
- Confirm that compose.yaml is saved in the docker-counter workspace.
Help me troubleshoot my rendered Compose model.
Start the stack and follow its logs
Detached mode keeps the application running while returning control of the terminal. This lets you inspect service status without stopping the counter.
- Start the stack in the background by running this command:
docker compose up -d
What does detached mode do?
This command starts the configured services in the background. The terminal becomes available for inspection commands while the counter continues serving requests.
The terminal returns to its prompt after the services start. Your stack remains active in the background.
Stack did not start?
- Confirm that Docker Desktop is still running.
- Review the terminal output for the service that failed to start.
Help me troubleshoot the detached Compose startup.
- List the current service state by running this command:
docker compose ps
What does the service list prove?
This command lists the project containers with their current status. It also shows the published web port.
You should see both the web service and Redis running. Redis should show a healthy status.
Redis is not healthy?
- Wait for the Redis health check to finish.
- Run the service status command again after the health check completes.
Help me troubleshoot the Redis health status.
Following logs keeps the terminal attached to live service output. You can stop following the stream after a browser request appears.
- Follow the live service logs by running this command:
docker compose logs -f
What does log following show?
This command streams new output from the running services. A browser refresh produces a new web request entry in that stream.
- Return to the counter at http://localhost:8000 in Microsoft Edge.
- Refresh the page once.
- Switch back to the integrated terminal.
- Stop following the logs by pressing Ctrl+C.
You should see a new web request entry from the refresh. The containers keep running after you stop following the logs.
No web request in the logs?
- Confirm that the counter page loads at http://localhost:8000.
- Refresh the counter after the log stream starts.
Help me trace the missing web request log.
That request log connects a browser action to activity inside the web service. You can now inspect the state that request changed.
Read the live Redis counter
The browser displays the result of incrementing the hits key. Reading that key directly proves the page reflects the value stored in Redis.
Before you run this, which number do you expect Redis to return? Keep that prediction in mind.
- Read the live counter from Redis by running this command:
docker compose exec redis redis-cli GET hits
What does this command inspect?
- Compose executes the Redis command inside the running redis service.
- The command reads the current hits value without incrementing it.
The terminal prints the same numeric value most recently shown in Microsoft Edge. You have now traced the counter from the browser to its live stored state.
Counter values do not match?
- Compare the terminal value with the number from the most recent browser refresh.
- Run the Redis inspection command again after any additional refresh.
Help me reconcile the browser and Redis counter values.
Secret mission
Add a Dependency-Aware Health Endpoint
Your containers can be running even when the web application cannot reach Redis. Add a dependency-aware health endpoint that proves the connection works and reports a clear failure when it breaks.
Clean Up Your Resources
Clean Up Your Resources
This project runs locally in Docker Desktop. It creates no Azure charges.
Choose whether to keep the local stack running, pause it for later, or remove its containers and stored counter.
Resources you used:
- The web container that serves your Flask counter.
- The Redis container that stores the live visit count.
- The Docker Compose network that connects the two services.
- The named volume called redis-data that preserves the hits value.
Keep everything running
No action is needed. Choose this if you still want to demonstrate the counter or its dependency-aware health endpoint.
- Leave the running Compose stack as it is.
- Keep Docker Desktop running while you want the application to remain available.
- Return to http://localhost:8000 whenever you want to demonstrate the counter.
- Visit http://localhost:8000/health whenever you want to confirm that the application can reach Redis.
Pause - I'll come back to this later
Stop the running services to free up system resources. The containers and redis-data volume stay available for your return.
- In Visual Studio Code, select Terminal from the menu bar.
- Select New Terminal.
- Stop both services by running:
docker compose stop
What Does This Command Do?
This stops the web and Redis services without removing their containers or the stored counter.
- Restart the existing containers when you return by running:
docker compose start
What Happens When You Restart?
Compose starts the existing service containers. Redis reads the preserved counter from redis-data.
Delete - I don't want to use this again
Remove the Compose containers, network, and redis-data volume when you want to clear the runtime resources. The stored count disappears permanently, so this option fits only when you have finished with the demo.
- In Visual Studio Code, select Terminal from the menu bar.
- Select New Terminal.
- Remove the running stack and its stored counter by running:
docker compose down -v
What Gets Deleted?
This removes both service containers and the Compose network. The -v flag also removes redis-data, which permanently deletes the stored hits value.
That is the destructive cleanup finished. Your docker-counter source files and locally built web image remain available for a future rebuild or manual image cleanup.
Nice Work!
Nice Work!
You did it! Your Flask visit counter now runs as a reliable two-service Docker Compose stack. Its dependency-aware health endpoint proves whether the application can reach the Redis-backed count.
You've learned how to:
- Build a Flask visit counter inside a Linux container from your own Dockerfile.
- Move the count into Redis to reveal data loss when a container is replaced. Preserve the live hits value across stack recreation with the redis-data named volume.
- Coordinate startup with a Redis health check. Gate the web service with the service_healthy dependency. Inspect the resolved Compose model. Confirm container health. Follow request logs. Read the live hits value from Redis.
- Secret Mission: Add a dependency-aware health endpoint at /health that reports healthy Redis connectivity. Prove its failure signal through an intentional hostname misconfiguration.
Ready to quiz yourself?