Practical lab guide
MicroBank 2: build the local container profile
Package the real services with private databases, explicit health checks, and local ports.
Docker
PostgreSQL
Predict before you try
Can Accounts acceptance alone prove this saved deposit reached Ledger exactly once?
Try it: Build Ledger without the old single-stage image →On this page
Use one standalone Compose file
Work from the MicroBank repository root prepared in step 1. This profile is separate from upstream docker-compose.yml; always select it explicitly. It avoids inherited public port bindings, the old initializer, and the unused notifications database. Terraform will own messaging resources in step 3.
For the first run, use the downloadable starter from step 1. It supplies the exact files shown below and pinned Floci/PostgreSQL image digests. python3 scripts/study/starter.py prepare creates .study.env without overwriting an existing file. The Floci runtime uses dummy AWS credentials and requires no LocalStack token.
The starter uses a fixed project name, learnwithsk-microbank-v1, and host ports 24666, 28080, 28081. Run the prerequisite checker first; an occupied port is a reason to inspect its owner, not stop an unrelated service. Floci uses its image-provided health check and persistent storage at /app/data. No Docker socket is mounted.
Save infra/study/compose.yaml:
services:
floci:
image: ${FLOCI_IMAGE:?Run the starter preparation first}
environment:
FLOCI_STORAGE_MODE: persistent
FLOCI_STORAGE_PERSISTENT_PATH: /app/data
FLOCI_DEFAULT_REGION: ca-central-1
FLOCI_HOSTNAME: floci
FLOCI_BASE_URL: http://floci:4566
ports:
- "127.0.0.1:24666:4566"
volumes:
- floci-data:/app/data
healthcheck:
interval: 5s
timeout: 3s
retries: 30
mem_limit: 1536m
accounts-db:
image: ${POSTGRES_IMAGE:?Run the starter preparation first}
environment:
POSTGRES_DB: accounts_dev
POSTGRES_USER: postgres
POSTGRES_PASSWORD: study-only
volumes:
- accounts-data:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, "pg_isready -U postgres -d accounts_dev"]
interval: 5s
timeout: 3s
retries: 20
mem_limit: 512m
ledger-db:
image: ${POSTGRES_IMAGE:?Run the starter preparation first}
environment:
POSTGRES_DB: ledger_dev
POSTGRES_USER: postgres
POSTGRES_PASSWORD: study-only
volumes:
- ledger-data:/var/lib/postgresql/data
healthcheck:
test: [CMD-SHELL, "pg_isready -U postgres -d ledger_dev"]
interval: 5s
timeout: 3s
retries: 20
mem_limit: 512m
accounts:
image: microbank-study/accounts:${STUDY_TAG:-starter-v1}
build:
context: ../../services/accounts
env_file: runtime.env
ports:
- "127.0.0.1:28080:8000"
depends_on:
accounts-db:
condition: service_healthy
floci:
condition: service_healthy
healthcheck:
test: [CMD, python, -c, "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"]
interval: 5s
timeout: 3s
retries: 20
mem_limit: 512m
ledger:
image: microbank-study/ledger:${STUDY_TAG:-starter-v1}
build:
context: ../..
dockerfile: infra/study/Dockerfile.ledger
env_file: runtime.env
environment:
SPRING_JPA_HIBERNATE_DDL_AUTO: update
JAVA_TOOL_OPTIONS: "-XX:MaxRAMPercentage=60"
ports:
- "127.0.0.1:28081:8001"
depends_on:
ledger-db:
condition: service_healthy
floci:
condition: service_healthy
healthcheck:
test: [CMD, curl, -fsS, http://localhost:8001/v1/health]
interval: 5s
timeout: 3s
retries: 30
start_period: 30s
mem_limit: 768m
volumes:
accounts-data:
ledger-data:
floci-data:
The fixed study-only password is a disposable fixture value on unpublished database ports. It is not a production secret. Memory limits are initial lab guardrails, not measured requirements; build processes need additional memory. Diagnose exit 137/OOM events and host pressure before changing limits. Do not start the optional frontend while resolving backend startup issues.
Build Ledger without the old single-stage image
Save infra/study/Dockerfile.ledger:
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /src
COPY services/ledger/pom.xml ./pom.xml
COPY services/ledger/src ./src
RUN mvn --batch-mode verify
FROM eclipse-temurin:17-jre-jammy
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=build /src/target/ledger-service-1.0.0.jar ./app.jar
USER 10001:10001
EXPOSE 8001
CMD ["java", "-jar", "app.jar"]
The inspected project has no Ledger test suite. mvn verify compiles/packages it and runs any tests you subsequently add; a successful build alone is not business-behavior coverage. The Compose override replaces create-drop with update for this local persistence exercise. Versioned database migrations are a later improvement; update is not a production migration strategy.
Optional frontend scope
The downloadable profile verifies the two backend APIs. The existing Auth0 frontend is a later extension with its own build-time Vite inputs and callback/CORS settings. It is not declared in this backend-only Compose file. Its sample notifications and initial balance display do not establish the Ledger result.
Save this as the root .dockerignore, and use the same exclusions in services/accounts/.dockerignore for that separate build context:
.git
.env*
**/.env*
.study.env
**/node_modules
**/target
**/__pycache__
infra/study/runtime.*
infra/study/terraform/.terraform
infra/study/terraform/*.tfstate*
infra/study/terraform/*.tfplan
artifacts
evidence
Docker ignores are relative to each build context. Record resolved base-image digests in your evidence; tags shown here are starting selections, not immutable pins.
Read the profile as a set of separate lifetimes
The two database services store application data in named volumes. Removing an application container need not erase those volumes. Floci holds the emulated API resources; this profile explicitly enables persistent storage. The API images hold packaged code, while runtime.env supplies settings generated later. The optional frontend has a different build-time configuration path through Vite.
The health checks answer dependency readiness questions; they do not prove a deposit works. Database ports remain inside the Compose network, while deliberate host publications use loopback. In a container, the database service name identifies a peer; localhost would identify that container itself.
Trace the selected Compose file through compose.sh before running it. Its explicit project and file selection are what keep these commands attached to the learning profile. A command accidentally using the upstream Compose file can select different services and initialization behavior.
Validate and start infrastructure only
Save scripts/study/compose.sh to make the selected file and project name explicit:
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
exec docker compose --project-name learnwithsk-microbank-v1 \
--env-file "$root/.study.env" -f "$root/infra/study/compose.yaml" "$@"
chmod +x scripts/study/compose.sh
./scripts/study/compose.sh config --quiet
./scripts/study/compose.sh up -d --wait floci accounts-db ledger-db
./scripts/study/compose.sh ps
Do not start Accounts/Ledger until Terraform and Ansible complete. Do not mount upstream infra/localstack/init.sh: there must be one owner of the topic/queue lifecycle. Check the Floci startup logs and pinned image identity if readiness fails. Avoid printing a fully resolved Compose configuration containing credentials into public evidence.
Checkpoint and sources
Before continuing to Terraform, confirm that the three infrastructure services are healthy, the database ports are not published to the host, and the emulator is reachable through loopback. Sources: Compose startup order↗, profiles↗, Floci setup↗, queue URL strategy↗, Spring database initialization↗, Vite environment variables↗, Auth0 React setup↗.
Prove what you learned
Inspect the result
Retain selected source/profile, case identity, expected balance and matching-entry count.
Explain it in your own words
Explain why your observation supports the stated outcome, and name one thing it does not prove.
Try a small variation
Repeat the same check after the documented restart or recovery; preserve the original case instead of creating a replacement.
Your evidence should show
- Record the context/revision or label the work as a paper or prepared fixture.
- Compare the prediction with the actual result from “Build Ledger without the old single-stage image”.
- Retain selected source/profile, case identity, expected balance and matching-entry count.
- Explain the transfer challenge and retain the same verification question.
Hint
Follow the case identifier across the outbox, message route and Ledger result.
Answer guide: compare after predicting
No. Repeat the idempotency key, then verify exactly one matching Ledger entry and the expected balance.
Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.
Recall private networking, persistent volumes and bounded Python checks. Revisit the previous lesson.
Save the prediction, result and explanation ↓Your notes and evidence
Record observations, questions, or links to your work. Keep credentials out of your notes.
Back up or restore this path
Progress and notes stay in this browser. A backup contains only this learning path.