Harden OrderFlow Service Images

Harden OrderFlow Docker images with non-root users and API health checks.

Introduction

30 Second Summary

Something can look reliable from the outside while risky defaults remain hidden underneath. Those defaults are easy to miss when you inherit a system that already works.

In this project, you will harden the Docker images for the Order, Inventory, and Catalog services. Your final audit will prove that the inherited checkout flow still works after every image change.

What You'll Build

You will run an audit from one terminal that shows three healthy non-root APIs while the Inventory worker still processes the next checkout event.

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

  • A reproducible image build whose audit reveals a fixed base digest and focused build contexts. A second build demonstrates build cache reuse.
  • Verified non-root execution using UID and GID 10001. Docker Compose shows all three APIs as healthy.
  • A post-hardening checkout demo that proves the RabbitMQ event still reaches the Inventory worker.
  • Secret Mission: Break the Order API health check on purpose. Inspect the stored failure evidence before repairing it.

Are there any prerequisites?

You need the completed OrderFlow Projects 1 through 5 workspace. The finished persistent-checkout workspace must include the inherited RabbitMQ event flow and Inventory worker.

You also need Linux with Docker Engine and Docker Compose ready. You should be comfortable editing Dockerfiles in Visual Studio Code.

Before We Start

Before the hands-on work begins, lock in the inherited checkout, catalog, cache, RabbitMQ event flow, and Inventory worker behavior that every image change must preserve. This checkpoint commits you to hardening the existing application images.

Restore the Project 5 Baseline

Container hardening is only meaningful after the inherited application works. A proven baseline separates Project 5 faults from changes introduced later.

The completed persistent-checkout workspace is your control case. Its inherited worker source must be present before you run the stack.

You will inspect the workspace in Visual Studio Code. You will then verify Docker Engine.

A separate check confirms Docker Compose. The final startup proves the local stack can reach its inherited running or healthy state.

In this step, get ready to:
  • Confirm the completed persistent-checkout workspace contains every inherited artifact.
  • Verify the local container tooling is available.
  • Prove the inherited stack reaches a running or healthy state.
Open the inherited workspace

The left file tree is your quickest proof that you opened the correct Project 5 workspace. It should show the Compose files beside the three service directories.

  • Start Visual Studio Code from your Linux application launcher.

You will see Visual Studio Code's start view. The application is ready to open the inherited workspace.

  • Use the top menu's folder-opening option.
  • Select the local persistent-checkout folder.

The left sidebar updates to show the contents of persistent-checkout.

  • Confirm the file tree lists compose.yaml.,
  • Confirm the file tree lists compose.override.yaml.
  • Confirm the file tree lists order-service, inventory-service, and catalog-service.
  • Expand inventory-service in the left sidebar.
  • Confirm the inherited Inventory worker source from Project 5 is present.

Good work. You now have every inherited artifact in view before any image changes begin.

Missing an inherited file?

  • Confirm the selected folder is named persistent-checkout.
  • Return to your completed Project 5 copy if a required file or the Inventory worker source is absent.

Ask for help identifying the correct workspace before continuing.

Verify Docker readiness

The Docker client must reach Docker Engine before Compose can start the inherited services. The Compose plugin must also be available in the same terminal.

  • Use Visual Studio Code's top menu to create an integrated terminal for the open persistent-checkout workspace.
  • Verify Docker Engine connectivity by running this command:
docker version

What does this verify?

The Client section confirms the command-line tool is available. The Server section confirms it can communicate with Docker Engine.

  • Verify Docker Compose availability by running this command:
docker compose version

What does this verify?

This check prints the installed Docker Compose version information. Seeing a version confirms the Compose plugin is ready to interpret the inherited files.

Docker check not completing?

  • Confirm Docker Engine is running through your Linux service manager if the first check does not show server information.
  • Confirm Docker Compose remains installed if the second check does not print version information.
  • Rerun the matching check after correcting the local tool state.

Help me troubleshoot the Docker readiness checks.

Start the inherited stack

A rendered Compose model shows which services Docker Compose resolves before it changes the running stack. This catches a wrong folder or invalid inherited configuration early.

  • Render the inherited service list by running this command:
docker compose config --services

What does this command prove?

This read-only check renders the applied Compose model. The --services flag prints the resolved service names one per line.

You should see the inherited services. These include order-service, inventory-service, catalog-service, and inventory-worker.

Your inherited data is safe during this startup. The next command builds images without deleting named volumes.

Before you start the stack, do you expect the command to return before every service reaches its running or healthy state?

  • Build the inherited images and start the full stack by running this command:
docker compose up --build --wait

What does this command do?

  • The --build flag builds the service images before startup.
  • The --wait flag waits until services are running or healthy.
  • The command keeps every inherited named volume available.

You will return to the shell prompt after the inherited services reach their required states. That successful return proves the baseline can start as a complete stack.

Before you review the status, which inherited services do you expect to see running or healthy?

  • Review the current service states by running this command:
docker compose ps

What should I see?

  • The inherited OrderFlow services show running or healthy states.
  • The inherited PostgreSQL named volumes remain intact.
  • The inherited Redis state remains intact.
  • The inherited RabbitMQ state remains intact.
  • The inherited application-data volumes remain intact.

Stack not reaching a ready state?

A missing service usually means the terminal is outside persistent-checkout or an inherited Compose file is absent. A service that stops points to a Project 5 baseline issue that should be resolved before hardening begins.

Help me diagnose the inherited stack status.

Your inherited OrderFlow stack is now running from a verified baseline. Next, you will inspect its runtime identity to expose the root-user gap.

Expose the Root-User Gap

Your inherited OrderFlow stack is running under Docker Compose. Its Project 5 behavior now has a trusted baseline.

A working container can still run with more privilege than its application needs. A running process also does not prove that an API accepts connections.

In this step, get ready to:
  • Measure the inherited Order container's numeric runtime identity.
  • Map each long-lived API or worker process to its Compose service.
  • Compare application process state with API health visibility.
Check the Order runtime identity

Each running process has a numeric user ID and group ID. These values reveal which account owns the application process inside the container.

Before you run this, make a quick prediction about the numeric user and group IDs you expect to see.

  • Return to the terminal panel in Visual Studio Code from the baseline check.
  • Measure the Order process identity by running this command:
docker compose exec order-service python -c "import os; print(os.getuid(), os.getgid())"

What Does This Command Check?

  • The probe runs inside the existing order-service container.
  • The os.getuid() function reads the process's real user ID.
  • The os.getgid() function reads the process's real group ID.

You'll see 0 0. The first value is the real user ID. The second value is the real group ID.

You have now captured the intended security shortfall. The inherited Order application starts with the root user and group.

Command Cannot Reach Order?

  • Confirm that the inherited stack remains running.
  • Check that the service name is exactly order-service.
  • Ask for help with the running service check.
Map the application processes

The stack contains three API services plus a dedicated Inventory worker. A live process audit shows whether those responsibilities still occupy separate service boundaries.

  • Display the running Compose processes by running this command:
docker compose top

What Does the Process List Show?

  • The command reads the live process table for every running Compose service.
  • Each row exposes the command started by its service.
  • The Inventory API and worker can share an image while retaining separate process boundaries.
  • Find the long-lived API process for order-service.
  • Find the long-lived API process for inventory-service.
  • Find the long-lived API process for catalog-service.
  • Find the separate worker process for inventory-worker.

You should find one long-lived application process under each service. The Inventory worker remains separate from the Inventory API.

That process map confirms the API boundaries. It also preserves the worker as its own application process.

Missing an Application Process?

  • Confirm that the missing service still has a running container.
  • Review that service's recent startup output for an import or connection failure.
  • Ask for help interpreting the process list.
Audit API health visibility

A health check tests a condition beyond basic process state. Without an API probe, Compose cannot confirm that the service listener accepts connections.

Before you check, predict which application services have an explicit health result.

  • Display the current Compose service states by running this command:
docker compose ps

What Does Compose Status Prove?

  • Compose reports whether each service container is running.
  • A health result appears only when that service has a configured probe.
  • Process state is one signal. Health state is another.

The three application API rows have no explicit listener health result. Infrastructure services with inherited probes can still report healthy.

The second audit gap is now visible. The APIs are running without a direct check that their listeners accept connections.

Status Differs From the Baseline?

  • Compare the workspace with the completed Project 5 baseline if an application API already has a health result.
  • Restore the inherited stack if an expected service has stopped.
  • Ask for help comparing the service states.

You've now proved both inherited gaps with runtime evidence. Next, you'll harden the three service images using what this audit revealed.

Build Non-Root Images

The inherited OrderFlow stack is still running. Your audit proved that its Order API starts with UID 0 plus GID 0.

That result exposes unnecessary runtime privilege. Each Docker image needs a fixed base plus a narrow build context plus a deterministic runtime identity.

The dependency files stay unchanged. The Inventory worker continues reusing the Inventory image with its inherited command.

In this step, get ready to:
  • Filter generated files plus local secrets from each service build context.
  • Replace the three service Dockerfiles with digest-pinned non-root definitions.
  • Prove cache reuse plus the new runtime identity across all three APIs.
Create narrow build contexts

A .dockerignore file removes matched paths before Docker sends a service directory into the build. Smaller contexts reduce noise plus prevent local environment files from entering an image build.

  • In the file sidebar of Visual Studio Code from earlier, create a .dockerignore file inside each of order-service, inventory-service, plus catalog-service.
  • Add the same exclusions to all three files by copying this content:
__pycache__/
*.py[cod]
.pytest_cache/
.venv/
.env

What do these exclusions do?

  • The __pycache__/, *.py[cod], plus .pytest_cache/ patterns exclude generated Python cache data.
  • The .venv/ pattern excludes packages from a local virtual environment.
  • The .env pattern keeps local environment values outside the image build.
  • Save all three .dockerignore files.
  • Confirm the file sidebar lists one .dockerignore under every service directory.

Each file should contain five exclusions. The final exclusion should be .env.

Missing an ignore file?

Check that each filename begins with a full stop. Confirm that none of the files has an added extension such as .txt.

Help me check the three service-specific build contexts.

Replace the service Dockerfiles

Digest pinning gives every service the same immutable Python base. Dependency-first layer ordering lets Docker preserve the package-install layer when only application source changes.

  • In the file sidebar, select the existing Dockerfile inside each service directory.
  • Replace the contents of all three files with this same definition by copying the code below:
FROM python:3.13.15-slim@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=10001:10001 *.py ./

USER 10001:10001

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

What does this Dockerfile do?

  • The first line pins 3.13.15-slim to the verified base-image digest.
  • The dependency copy plus pip installation happen before the source copy so dependency changes control that expensive layer.
  • The source copy assigns numeric ownership 10001:10001 without requiring named users inside the image.
  • The USER instruction gives the runtime command the same numeric identity.
  • The EXPOSE instruction documents the API listener port.
  • The final command launches Uvicorn as the API process on port 8000.
  • Save the three Dockerfile files.
  • Select each saved Dockerfile in the file sidebar.

Each file should show the pinned Python base on its first line. The numeric runtime user should appear near the end.

Do the Dockerfiles differ?

Confirm that each service file has the same complete definition. Check that the source ownership matches the runtime user identity.

Help me compare my three hardened Dockerfiles.

✔️ Awesome, I've got everything!

Great. Double-check that all six files are saved before building the images.

ⓧ I'd like to double check the full code

Compare every service file with these complete references.

__pycache__/
*.py[cod]
.pytest_cache/
.venv/
.env

Order build-context check

This complete file defines the exclusions for the Order service build context.

FROM python:3.13.15-slim@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=10001:10001 *.py ./

USER 10001:10001

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Order image check

This complete file builds the digest-pinned non-root Order API image.

__pycache__/
*.py[cod]
.pytest_cache/
.venv/
.env

Inventory build-context check

This complete file defines the exclusions for the Inventory service build context.

FROM python:3.13.15-slim@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=10001:10001 *.py ./

USER 10001:10001

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Inventory image check

This complete file builds the shared Inventory image used by its API plus worker containers.

__pycache__/
*.py[cod]
.pytest_cache/
.venv/
.env

Catalog build-context check

This complete file defines the exclusions for the Catalog service build context.

FROM python:3.13.15-slim@sha256:9d2e5553305c7c7b0097999bb17187c69b921ccd6bc9d40e4bb5ebe652c00285

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY --chown=10001:10001 *.py ./

USER 10001:10001

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

Catalog image check

This complete file builds the digest-pinned non-root Catalog API image.

Build and verify the images

The build cache reuses an unchanged layer plus the layers that precede it. Two consecutive builds reveal whether the dependency-first ordering works.

  • Build the three application images from their new files by running:
docker compose build

What does this command do?

Docker Compose rebuilds every service image defined by the inherited Compose model. Each service uses its own directory as the filtered build context.

  • Review the completed output for all three application image builds.

The Order, Inventory, plus Catalog image builds should complete without a permissions or source-copy error.

Did the first build fail?

Check that every Dockerfile uses the complete pinned base reference. Confirm that each service directory still contains its inherited requirements.txt plus Python source files.

Help me diagnose the failed OrderFlow image build.

Before you rebuild, which Dockerfile sections do you expect Docker to reuse?

  • Build the unchanged images a second time by running:
docker compose build

Why run the build twice?

The second build tests whether unchanged inputs reuse their previous layers. It turns the Dockerfile ordering into visible evidence.

  • Review the second build output for the dependency-install plus source-copy steps.

Docker should mark the unchanged dependency plus source steps as cached. This confirms that identical inputs do not repeat those layers.

Are the layers rebuilding?

Confirm that no service file changed between the two builds. Check that each .dockerignore prevents local cache files from changing the build context.

Help me find why the second Docker build is missing cache reuse.

The rebuilt containers can now replace the inherited root-running containers. This startup keeps every inherited named volume attached.

  • Start the rebuilt stack while waiting for its services by running:
docker compose up --wait

What does this startup preserve?

Docker Compose recreates services from the rebuilt images. The command waits until services are running or their inherited checks report healthy.

The command does not request volume removal. Your PostgreSQL, Redis, RabbitMQ, plus application data remain attached.

  • Wait for the startup command to return successfully.

The application containers should now run from the rebuilt images. The Inventory worker remains part of the inherited stack.

Did the rebuilt stack stop?

Review the startup output for the first service that exited. A source import error can indicate that a required Python file was excluded or missed by the wildcard copy.

Help me diagnose the rebuilt OrderFlow stack startup.

Before you run the identity check, do you expect any API service to keep the inherited root identity?

  • Check the runtime UID plus GID inside all three API containers by running:
for service in order-service inventory-service catalog-service; do docker compose exec -T "$service" python -c "import os; print(os.getuid(), os.getgid())"; done

What does this verification prove?

The loop runs Python inside each API container. The two values come from the live application environment instead of the Dockerfile text.

The loop targets the three APIs. The Inventory worker continues using the hardened Inventory image with its separate inherited command.

You should see three 10001 10001 lines in service order. That closes the privilege gap for every application API.

Still seeing a root identity?

Confirm that the affected service rebuilt from the saved Dockerfile. Check that its file contains the numeric runtime user after the source copy.

Help me trace an unexpected runtime UID or GID.

Your three application images now use fixed base content plus deterministic non-root execution. Next, you will add API listener health checks without applying the same probe to the Inventory worker.

Add Observable API Health

Your hardened OrderFlow images now run as UID and GID 10001. That closes the unnecessary privilege gap across every application container.

A running process cannot prove that an API listener accepts connections.

A dedicated Docker Compose overlay adds container health checks to each API. The Inventory worker stays outside the listener probe.

In this step, get ready to:
  • Add a dedicated health-check overlay for the three OrderFlow APIs.
  • Validate the merged Compose model's probe scope.
  • Start the merged stack with health-aware waiting.
Create the API health overlay

The compose.images.yaml file is a YAML overlay for runtime health settings. Keeping these probes separate preserves the inherited service definitions.

  • Select the persistent-checkout workspace folder in the Explorer sidebar.
  • Create compose.images.yaml at the top level of that folder with the New File button.

You will see compose.images.yaml beside the inherited Compose files in the Explorer.

  • Add the API-only overlay by pasting this complete file:
services:
  order-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

  inventory-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

  catalog-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

What does this overlay do?

  • The test entry runs Python inside each API container. Its create_connection() call tests whether the listener accepts a TCP connection on 127.0.0.1:8000.
  • The interval value starts a new probe every 5s.
  • The timeout value gives each probe 3s to finish. The retries value allows 3 consecutive failures before the service becomes unhealthy.
  • The start_period value gives each API 5s to begin listening before failures count.
  • Only the three API services receive this listener check. The Inventory worker has no API listener to probe.
  • Save compose.images.yaml.
  • Confirm the file remains visible beside compose.yaml in the Explorer sidebar.

Good progress. Your API probes now live in a separate overlay without changing the inherited Compose files.

Overlay file in the wrong place?

Check that compose.images.yaml sits directly inside persistent-checkout. It should share the same folder level as compose.yaml.

Keep every setting beneath its matching healthcheck key. Help me check the location and indentation of my Compose health overlay.

✔️ Awesome, I've got everything!

Your compose.images.yaml file is saved at the top level of persistent-checkout.

ⓧ I'd like to double check the full code

services:
  order-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

  inventory-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

  catalog-service:
    healthcheck:
      test: ["CMD", "python", "-c", "from socket import create_connection; create_connection(('127.0.0.1', 8000), timeout=2).close()"]
      interval: 5s
      timeout: 3s
      retries: 3
      start_period: 5s

What should you compare?

Compare the indentation beneath each API service. Every service should contain the same five health-check settings.

Render the merged Compose model

Compose combines files in command-line order. Rendering the result proves where the overlay lands before any containers are recreated.

  • Validate the three-file Compose model by running:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml config

What does this command do?

The three -f arguments load the inherited files first. compose.images.yaml adds the health settings last.

The config command renders the final service model without changing the running stack. This makes the probe boundary visible before startup.

  • Check the rendered API service sections for one healthcheck each.
  • Check the rendered inventory-worker section for the absence of healthcheck.

That separation is confirmed. Each API owns a listener probe while the Inventory worker keeps its inherited worker command.

Rendered model missing a health check?

Confirm that the command includes compose.images.yaml after the two inherited files. Check that each API name matches its existing Compose service name.

Review the YAML indentation if the command reports a parsing problem. Help me diagnose why my merged Compose model is missing an API health check.

Start and verify the healthy stack

The --wait flag keeps the startup command active until services reach their required running or healthy states. This turns health status into a startup gate.

  • Rebuild the merged stack and wait for service readiness by running:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml up --build --wait

How does startup use the overlay?

  • The three -f arguments reproduce the validated merged model.
  • The --build flag rebuilds service images before startup. Unchanged Dockerfile layers can reuse the existing cache.
  • The --wait flag waits for running services. It also waits for configured health checks to succeed.
  • This startup leaves the inherited named volumes intact.
  • Confirm the command returns successfully after the merged services reach their required states.

Startup does not finish?

A pending startup usually means one API has not accepted its probe connection. Review the service output for the API that remains in its starting state.

Confirm that every probe targets 127.0.0.1:8000. Help me find which OrderFlow API is preventing health-aware startup from finishing.

Before you check the final state, predict which services will show a health label.

  • Display the merged stack's final service status by running:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml ps

What does this status show?

The ps command lists each Compose container with its current status. Services with configured probes include their health state.

You should see Order, Inventory, and Catalog report healthy. The Inventory worker remains a separately running service without the API listener health check.

One API is not healthy?

Check the affected API's service name in compose.images.yaml. Confirm its probe still targets port 8000.

Make sure the startup command includes all three Compose files in the same order. Help me diagnose an OrderFlow API that is running without becoming healthy.

Your API listeners now report their own health without changing the Inventory worker's job. Next, you will inspect image layers, runtime metadata, and process boundaries before repeating the checkout flow.

Audit Images and Processes

The hardened OrderFlow stack now runs three healthy APIs under a numeric non-root identity. The Inventory worker still runs as a separate service.

Listener health does not reveal how Docker assembled an image. It also cannot prove that the inherited RabbitMQ checkout path still reaches the worker.

In this step, get ready to:
  • Compare the image IDs used by the Inventory API and Inventory worker.
  • Audit the Order image from its build layers through its runtime identity.
  • Prove the checkout event still reaches the separate Inventory worker.
Compare the application images

Docker Compose can map every created service container to its local image ID. A matching pair reveals image reuse across the Inventory API and worker.

  • List the application images through the three-file Compose model by running this command:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml images

What does this command show?

The command applies the same three Compose files that started the hardened stack. The resulting table links each created service container to its image.

The image ID identifies the built image content. Services with the same image ID reuse the same local image.

  • Find the row for inventory-service.
  • Find the row for inventory-worker.
  • Compare the image IDs in those rows.

You should see the same image ID for the Inventory API and Inventory worker. You have your first audit result: both services reuse one hardened Inventory image.

Missing an image row?

  • Confirm the stack from the previous step is still running.
  • Check that you used all three Compose files in the same order.

Help me diagnose a missing service in my Docker Compose image list.

Inspect the Order image

Image history turns Dockerfile instructions into an audit trail. The image layers should show dependencies installed before the Python source is copied.

  • Inspect the Order image history by running this command:
docker image history "$(docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml images -q order-service)"

What does image history reveal?

The nested Compose query returns the current image ID for order-service. Docker then displays the instructions that produced its layers.

Layer order matters for caching. Stable dependency installation stays reusable when only the application source changes.

  • Locate the dependency-install layer tied to requirements.txt.
  • Locate the later Python source-copy layer.

The history should place dependency installation before the source copy. This order preserves the useful dependency cache when Python files change.

  • Inspect the Order image configuration by running this command:
docker image inspect "$(docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml images -q order-service)"

What does image inspection prove?

Image inspection exposes the stored configuration used when a container starts. This metadata provides evidence of the default runtime identity without entering a running container.

  • Search the terminal output for "User".
  • Confirm the complete entry reads "User": "10001:10001".

The inspected configuration confirms that the Order image defaults to UID and GID 10001. The non-root identity is now part of the image itself.

Image lookup returned nothing?

  • Confirm the order-service container exists in the current Compose project.
  • Return to the persistent-checkout workspace before retrying the audit.

Help me diagnose an empty Order image lookup.

Verify process boundaries and event flow

An image can support several service roles while each container keeps one primary responsibility. The process list makes that process boundary visible before you test the inherited checkout path.

  • Display the application processes across the hardened stack by running this command:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml top

What does the process list prove?

The process list groups running commands by Compose service. This lets you distinguish the three API processes from the separate Inventory worker process.

The Inventory services can share an image while running different application commands. Their separate containers preserve separate responsibilities.

  • Identify the long-lived application command for order-service.
  • Identify the long-lived application command for inventory-service.
  • Identify the long-lived application command for catalog-service.
  • Identify the long-lived application command for inventory-worker.

You should see one visible long-lived primary application command for each API or worker container. The Inventory worker remains distinct from the Inventory API.

Missing the worker process?

  • Check that inventory-worker still appears as a running service.
  • Review the worker logs for an import or startup failure.

Help me diagnose a missing Inventory worker process.

Before the regression check, consider this question: do the hardened image changes affect RabbitMQ event delivery.

  • In the terminal from earlier, repeat the exact final checkout request from completed Project 5.
  • Confirm the checkout request returns the same successful response you saw in Project 5.
  • Inspect the latest Inventory worker logs by running this command:
docker compose -f compose.yaml -f compose.override.yaml -f compose.images.yaml logs --tail=20 inventory-worker

What does this log check prove?

The command shows the latest 20 lines emitted by the Inventory worker. A new processed order event proves the worker still consumes checkout messages after image hardening.

This regression check reaches beyond listener health. It exercises the inherited broker path through the worker.

You should see the new order event processed in the worker logs. You should not see permission, import, database, or broker errors tied to that event.

No new event in the logs?

  • Confirm the checkout request completed successfully before reading the worker logs.
  • Check that the latest log lines belong to the new order event.
  • Review the worker output for a permission, import, database, or broker failure.

Help me trace a missing checkout event in the Inventory worker logs.

That completes the audit. Your hardened stack still processes the inherited checkout event through its separate Inventory worker.

Secret mission

Break and Repair an API Health Check

A running API can still fail its configured health check. Break the Order probe on purpose, inspect Docker's stored failure evidence, then restore the listener check and return Order to healthy status.

Clean Up Your Resources

No action is needed. Choose this option if you want to continue inspecting the hardened OrderFlow stack.

  • Leave the service containers running so the APIs and Inventory worker remain available.
  • Keep the hardened Dockerfiles, .dockerignore files, and compose.images.yaml in the persistent-checkout workspace.
  • Retain the inherited named volumes so the PostgreSQL, Redis, RabbitMQ, and application data remain available.
  • Continue using the stack at no ongoing cost because every resource runs locally.

Nice Work!

Nice Work!

You did it! Your OrderFlow Project 6 stack now runs hardened Docker service images without losing the inherited Project 5 RabbitMQ checkout flow.

What you learned:

  • Created digest-pinned service images with narrow build contexts. Confirmed build cache reuse on an unchanged rebuild. Verified that every application container runs as UID and GID 10001.
  • Added API-specific health checks for Order, Inventory, and Catalog. Preserved the separate Inventory worker process without attaching an API listener probe to it.
  • Built an image and process audit trail across image history, image metadata, and live process output. Repeated the inherited checkout smoke test to prove the hardened stack still delivers events to the Inventory worker.
  • Secret Mission: Broke the Order API probe on purpose. Used Docker health evidence to explain why the container became unhealthy while its API process kept running. Restored port 8000 to return Order to healthy status.

Ready to quiz yourself?