The Repo That Runs the Other Twelve
Pacco's umbrella repository contains no code, yet it decides everything about how the estate actually runs - seven compose files in deliberate layers, two PM2 manifests that quietly forget the saga service, a 460-line docker run-book, and a .rest file that is the best onboarding document in the system.
Part 10 read the Pacco umbrella as an assembly problem — clone scripts, a solution file of ..\ paths, a floating 0.4.* wildcard. But assembly is only half of what the Pacco repo does. The other half is runtime: this repository of zero C# files is where the estate's operational truth lives — which containers start, in what order, on which network, with which gateway configuration, and which service quietly doesn't start at all. Every microservices estate ends up with a repo like this, usually by accident and usually undocumented as a genre. Pacco's is worth reading end to end, because it demonstrates both the craft the genre demands and the drift the genre invites.
Three ways to run one estate
The umbrella supports three distinct runtimes, and the files divide cleanly between them:
| Runtime | Files | What runs |
|---|---|---|
| Docker | compose/ — seven compose files |
infrastructure + all eleven app containers |
| Raw processes | each repo's scripts/start.sh, or F5 |
whatever you started by hand |
| PM2 | services.yml, prod-services.yml |
ten apps under a Node process manager |
Three runtimes is generous for a teaching estate — it lets the course show dotnet run for the inner loop, Docker for the full skyline, and PM2 for the “processes on a VM” deployment style. It also means the estate's definition of itself exists in three copies, and we will see below that the copies disagree.
Compose in layers
The compose/ directory is not seven redundant files; it is a layering scheme. At the bottom, infrastructure.yml starts ten infrastructure containers — Consul, Fabio, Grafana, Jaeger, Mongo, Prometheus, RabbitMQ, Redis, Seq, Vault — and, crucially, defines the network:
networks:
pacco:
name: pacco-network
The application layer, services.yml, declares the same network external: true — it refuses to create it. That one keyword encodes the operational contract: infrastructure first, services second, always — the README documents the two-step bring-up, but the compose files enforce it. Start the services layer alone and Docker tells you the network doesn't exist, which is a better error than eleven apps crash-looping against a RabbitMQ that isn't there.
Alongside the full stack sit two variants. host-infrastructure.yml runs the same ten containers with network_mode: host — no port mappings, no container DNS — for the workflow where services run as raw processes on your machine and need localhost to mean the host. And three partial stacks slice the infrastructure by concern: mongo-rabbit-redis.yml (the minimum a service needs to boot — and the one partial that defines the network rather than joining it, making it the base layer), consul-fabio-vault.yml (discovery and secrets), and grafana-seq-jaeger-prometheus.yml (observability). A newcomer who only wants to debug one service against Mongo and Rabbit pays for three containers, not ten. That is genuine consideration for the inner loop, and most real estates never bother.
The layers drift, though, in a way worth pausing on. The full stack builds RabbitMQ as a custom image — FROM rabbitmq:management plus an enabled_plugins file that switches on the Prometheus plugin. The partial mongo-rabbit-redis.yml runs stock rabbitmq:3-management: no plugin, no metrics port. Same logical layer, two different brokers depending on which file you typed. Nothing records that difference; you discover it when a dashboard is empty. A compose variant is a fork, and forks need a diff-owner — the umbrella has five overlapping definitions of “the infrastructure” and no mechanism keeping them equivalent.
One precision the estate's own folklore gets wrong: the containers are not uniformly stateless. Mongo and Redis mount real named volumes (mongo:/data/db, redis:/data) in both the full and partial stacks — your domain data and Redis state survive a docker-compose down. It is everything else whose volume blocks are present and commented out. That distinction — durable business data, evaporating telemetry — is the whole subject of part 12.
Image or build - the estate in two moods
The application layer itself comes as a pair. services.yml pulls published devmentors/* images from Docker Hub — the estate as consumers experience it. services-local.yml declares the same eleven services with build: contexts that reach out of the repository entirely:
orders-service:
build: ../../Pacco.Services.Orders
container_name: orders-service
ports:
- 5006:80
../../Pacco.Services.Orders only resolves if every repo is cloned as a sibling — the same filesystem contract the solution file and clone scripts assume, now load-bearing at container-build time. The pair also encodes the estate's port map (5000 for the gateway, 5001–5009 for services, then a jump to 5015 for OrderMaker — five ports of history nobody kept) and one genuinely consequential disagreement: the hub-image file sets the gateway to ntrada-async.docker.yml while the local-build file sets ntrada.docker, so whether a POST becomes a bus message or an HTTP proxy call depends on which compose file you ran — the finding that opened part 1 and whose mechanics part 3 unpacked. The only orchestration hint anywhere is operations-service declaring depends_on all eight of its siblings — the estate's nervous system marked, topologically, as the thing that comes last.
The manifest that forgot the saga
The two PM2 manifests are my favourite artefacts in the repo, for what they include and what they omit. PM2 is a Node.js process manager, and using it to shepherd .NET services is a quietly pragmatic move: you get restarts (max_restarts: 3), a process list, and log capture without systemd units or Windows services. services.yml runs dotnet run in each repo's Api directory; prod-services.yml runs published DLLs and pins each service's port:
- name: orders
script: dotnet Pacco.Services.Orders.Api.dll
cwd: ../Pacco.Services.Orders/src/Pacco.Services.Orders.Api/bin/release/netcoreapp3.1/publish
max_restarts: 3
env:
ASPNETCORE_URLS: http://*:5006
Count the apps, though: ten. The gateway plus nine services. Both manifests omit ordermaker — the Chronicle saga service that drives a whole order through the estate. Under Docker, OrderMaker runs; under PM2, it does not exist, and nothing fails to tell you so. Every service it would have commanded still works individually; the composition they were supposed to perform simply never begins. (What that saga does when it does run — and its own honest ledger — is the saga that rides a header, over in the Convey-consumer series.) The lesson generalises brutally: when a service's only job is orchestration, forgetting to run it produces no errors, only silence — the estate looks healthy by every per-service measure while its headline behaviour is absent. A run manifest needs the same review discipline as code, because omissions in it are invisible at runtime.
A run-book of roads not taken
docker-images.txt is 460 lines of plain text: a docker run command, with optional volume and credential flags, for every piece of infrastructure — and then some. It documents SQL Server, PostgreSQL, InfluxDB, Mongo Express and a full ELK stack that no compose file starts and no service uses; every service's config carries the matching elk.enabled: false and influxEnabled: false blocks. The run-book is the estate's memory of roads not taken, and I mean that as description, not mockery — you can watch the authors' infrastructure shortlist fossilise in a text file.
Its best section is the one the running estate ignores: a complete production Vault walkthrough — vault operator init, unsealing, userpass auth, a services policy, the KV v2 upgrade, a database secrets engine issuing per-service MongoDB credentials on a one-hour TTL, and a PKI engine cutting *.pacco.io certificates. It even preserves example unseal keys and a root token from a demo run, committed to the repo — named here as a finding, not reproduced as anything usable. Part 8 measured the estate's actual posture against this document; from the umbrella's vantage the point is simpler: the run-book describes an estate that was designed and never deployed, and nothing marks which paragraphs are aspiration.
Onboarding you can execute
The artefact I would actually steal lives one repo over, but the umbrella's README points every newcomer at it, so it belongs to this story: Pacco-sample-scenario.rest in the gateway repo. It is the entire business domain as twenty-four chained HTTP requests in VS Code's REST Client format — sign up, sign in and capture the token, complete customer registration, add a parcel and capture its id from the Resource-ID response header, create an order, attach the parcel, add a vehicle, register it as an available resource, assign it, reserve the delivery date, run the delivery to completion, and finally fetch the order to see it marked completed:
### Add a parcel and grab its id
# @name add_parcel
POST {{api}}/parcels
...
@parcelId = {{add_parcel.response.headers.Resource-ID}}
Each response feeds the next request by reference. That makes the file three things at once: executable documentation of the choreography, a smoke test of the whole estate, and the fastest possible answer to "what does this system do?". A flat 288-line Pacco.rest catalogues every endpoint besides. Between the two-step compose bring-up and this file, time-to-first-request for a newcomer is minutes — twenty-one containers and a coffee. The best onboarding document in the estate is not a document; it is a scenario you run — and unlike the README's repo list, which part 10 caught disagreeing with reality, this artefact cannot drift silently, because drift makes it fail in your editor.
So the umbrella verdict mirrors the polyrepo one: real craft — layered compose, partial stacks, three runtimes, executable onboarding — with no mechanism holding the copies together. Five infrastructure definitions, two service definitions, three runtime rosters, one text-file memory; each maintained by hand, each drifting at its own pace. The next part follows one of those drifts to its sharpest consequence: an estate instrumented to the eyebrows — Jaeger tracing every request, Prometheus scraping every five seconds, Seq collecting every log — that forgets everything it observed the moment you type down. Next: observability with amnesia.