Skip to content
← DevOps foundations

Practical lab guide

MicroBank 1: prepare the application project

Build a local Accounts-to-Ledger implementation using the foundations modules.

Documentation reviewed2026-10-01 · 6 min read · lab time varies
On this page

What you will implement

Use MicroBank as the guided project for DevOps foundations. Complete the six concept modules, then work through these seven implementation labs in order. You will create a dedicated local Compose profile, provision its AWS-like dependencies with Terraform, render application configuration with Ansible, exercise a transaction with Python, and build reviewable artifacts through GitHub Actions.

The acceptance milestone is concrete: create a synthetic user/account, submit a deposit, repeat the same idempotency key, observe exactly one corresponding Ledger entry and the expected balance, restart the services, and verify the same data remains. Record the commands and actual results. This is a learning application using synthetic data.

StepImplementationFoundations applied
1Prepare the pinned source and publisher lifecycleGit, Python, application inventory
2Build a separate Compose profileDocker, networks, volumes, readiness
3Create topics, queues, policies, subscriptions, and a bucket in LocalStackAWS resource models and Terraform
4Render runtime configuration from infrastructure outputsAnsible configuration management
5Run a bounded transaction probe and its unit testsPython automation and API contracts
6Build images in CI and deploy a selected artifact locallyGitHub Actions and artifact identity
7Rehearse service failure, persistence, recovery, and cleanupOperational diagnosis

Use the Next links to follow this sequence. The smaller module labs prepare you for each tool used here.

Read the application route before the tool sequence

The local transaction travels from a synthetic Python caller to Accounts and its database, through SNS and SQS in LocalStack, to Ledger and its database; settlement events have no Accounts consumer in the inspected baseline.

  1. Accounts accepts the request and records work in its database, including the outbox used by its publisher.
  2. The publisher sends an event through SNS and SQS in LocalStack. This separates the HTTP response from Ledger's later processing.
  3. Ledger consumes the event and records the entry and balance. The probe checks that result directly.
  4. Settlement events have no Accounts consumer in this revision. An Accounts pending status and a completed Ledger entry can therefore coexist.

Open the topology. The diagram describes the implemented local target and its known missing connection. It is not a screenshot or a claim that the stack has been run.

Before writing files, explain why the tools come in this order: Compose starts the local dependencies; Terraform creates their API resources; Ansible turns outputs into application settings; the services can then start; Python checks a transaction. Starting the APIs before those inputs exist makes later failures harder to interpret.

Establish the source baseline

Start in a new directory on your Mac. If microbank-foundations exists, choose another destination. The pinned revision makes the guide's observations reproducible; inspect differences before using a newer application revision.

bash
git clone https://github.com/santoshkatageri/MicroBank.git microbank-foundations
cd microbank-foundations
git switch -c study/devops-foundations f75da680251eebeb38bf17bce3e49b2fea8385c0
mkdir -p infra/study/terraform infra/study/ansible/templates scripts/study tests/study evidence

Record git rev-parse HEAD, your host architecture, Docker/Compose, Terraform, Ansible, and Python versions in evidence/environment.md. Use Python 3.11 or newer for the probe, Terraform 1.12 or newer within 1.x, and a current Docker Compose v2 supporting up --wait. Ansible runs on the Mac control machine; the playbook in step 4 configures local application files, not a remote VM.

OrbStack supplies the Linux container engine. On the 16 GB host, run this track alone and build images sequentially. Check docker context show, docker version, docker stats --no-stream, and available disk space. Do not stop unrelated workloads or assume a fixed memory allocation will always fit. The browser profile is optional; the backend flow is the required milestone.

Add these entries to a new root .gitignore, or merge them into an existing one:

gitignore
.study.env
infra/study/runtime.env
infra/study/runtime.json
infra/study/terraform/.terraform/
infra/study/terraform/*.tfstate*
infra/study/terraform/*.tfplan
evidence/*.json
artifacts/
**/__pycache__/
**/node_modules/
**/target/

Commit the Terraform dependency lock file after initialization. Keep sanitized evidence Markdown in Git. Files already tracked by the source are not removed by .gitignore; inspect staged changes before committing.

Separate working services from unfinished features

The inspected source contains Accounts (FastAPI), Ledger (Spring Boot), a React/Vite frontend, three database declarations, and LocalStack initialization. The foundations profile uses only the two databases needed by the available backend services.

  • Accounts writes an outbox record; its publisher sends to SNS. Ledger reads an SQS queue and expects an SNS envelope containing a Message field.
  • Ledger publishes settlement events. No Accounts settlement consumer is present, so the Accounts transaction can remain pending even when a Ledger entry exists. Verify the Ledger result directly.
  • There is no notifications application service in this snapshot. The frontend notification component uses sample notifications, and its account hook initializes the displayed balance to zero without a Ledger balance fetch. These are explicit application backlog items, not completed integration checks.
  • The inspected API routes have no token-validation dependency. This profile binds APIs to loopback and uses synthetic data. Auth0 login in the frontend does not establish backend API authorization. Public cloud exposure requires separate application security work.

Manage the Accounts publisher lifecycle

The source starts its publisher task without retaining it for explicit shutdown. A synchronous startup callback is not itself proof of a failure: the inspected Starlette startup implementation calls such handlers from its async startup routine. For this lab, make task ownership and cleanup explicit. Replace services/accounts/app/main.py with the following lifecycle-aware version. It starts the publisher in the running event loop and cancels it when the application exits; it retains the existing routers and health response.

python
import asyncio
from contextlib import asynccontextmanager, suppress

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

from .controllers import user_controller, account_controller, transaction_controller
from .database import create_tables
from .outbox_publisher import publish_outbox_events


@asynccontextmanager
async def lifespan(app: FastAPI):
    create_tables()
    publisher = asyncio.create_task(publish_outbox_events())
    try:
        yield
    finally:
        publisher.cancel()
        with suppress(asyncio.CancelledError):
            await publisher


app = FastAPI(title="Accounts Service", lifespan=lifespan)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:8080"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
app.include_router(user_controller.router)
app.include_router(account_controller.router)
app.include_router(transaction_controller.router)


@app.get("/health")
def health_check():
    return {"status": "healthy"}

This lifecycle refactor preserves the publisher implementation: its synchronous database/SDK calls, retries, and reliability still deserve later application work. Health remains a process check. The transaction probe in step 5 is the integration check.

Walk through the lifecycle change

Read the replacement application file in three parts. The lifespan function starts the existing publisher and retains the task object. The application then serves its existing routers and health endpoint. On shutdown, the cleanup cancels and awaits that task, treating cancellation as an expected lifecycle event.

Retaining a task makes its owner explicit; it does not redesign the publisher's retries, synchronous calls, or transaction reliability. Check your diff for the intended lifecycle change and retained router registrations. If importing or compiling the file fails, resolve that before container builds—an image cannot repair an invalid application module.

Checkpoint

Your branch has the pinned baseline, a documented dependency inventory, ignored generated files, and the reviewed lifecycle change. Inspect the diff and make a focused commit before continuing. Do not mark a deployment successful until the later execution gates pass.

Sources

Inspected MicroBank source↗, FastAPI lifespan↗, Starlette 0.27 startup implementation↗, OrbStack Docker↗. These implementation labs are newly written for this application and build on the attributed foundations lessons. Status: documentation-reviewed; record runtime results for your selected versions.

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.