Practical, hands-on labs to learn distributed system and system design concepts by running real services, testing failures, and documenting trade-offs.
- Database replication (physical and logical)
- Queueing and stream processing (RabbitMQ, Kafka)
- Caching and proxy behavior (Nginx cache)
- Resiliency patterns (distributed circuit breaker)
- Observability basics (Redis pub/sub metrics stream)
- Kubernetes deployment fundamentals
db-replication-lab/- PostgreSQL primary/replica setuplogical-replication-lab/- PostgreSQL publication/subscription flowdistributed-cb-lab/- Redis-backed distributed circuit breakernginx-cache-lab/- Nginx reverse proxy cache behaviorkafka/- Kafka producer and consumer-group demoqueue/- RabbitMQ work queue producer/consumer demoredis-monitor/- Redis pub/sub metrics + WebSocket dashboardkube/- Kubernetes deployment and NodePort service basicsdocs/- Roadmap, architecture, runbook, troubleshooting, experiments
nginx-cache-labqueuekafkaredis-monitordistributed-cb-labdb-replication-lablogical-replication-labkube
- Podman (recommended on Linux) or Docker
- Node.js 18+ and npm
psqlclient for PostgreSQL labs- Optional: local Kubernetes (
kindorminikube) forkube/
These labs were built and validated with a Podman-first workflow.
- Rootless by default: safer local development because containers do not require a privileged daemon.
- No
docker.sockpermission friction: avoids the common Linux flow of running Docker commands withsudoor adding your user to thedockergroup (which effectively grants root-equivalent daemon access). - Daemonless model: fewer background moving parts and easier process-level visibility.
- Systemd-friendly: better integration with Linux service tooling.
- SELinux-aware workflow: volume relabeling options like
:zare explicit and important for bind mounts.
If you already use Docker, most commands remain conceptually identical. On Linux, Podman is often a cleaner default for local learning environments.
- Running labs without
sudoremoved a lot of day-to-day friction and made command behavior more predictable in a normal user shell. - Rootless containers made local experimentation feel safer when testing networking, replication, and message broker setups.
- SELinux mount labeling (
:z) is not optional on many Linux hosts; adding it early avoided confusing permission errors while bind-mounting project files. - Podman command parity (
podman compose,podman build,podman ps) made it easy to transfer Docker knowledge while keeping a Linux-native workflow.
Use per-lab README files for exact steps. A cross-lab runbook is in:
docs/running-labs.md
- RabbitMQ includes a built-in management UI, but Kafka does not expose a UI on the broker port by default.
- If you open the Kafka broker port in a browser and send an HTTP
GET, Kafka treats it as an invalid protocol request, so logs can look noisy/confusing (yes, this one can be hair-pulling at first). - Use a dedicated UI container/tool for inspection instead, for example
kafka-ui.
- Learning path:
docs/learning-roadmap.md - Architecture map:
docs/architecture-overview.md - Troubleshooting:
docs/troubleshooting.md - Experiments index:
docs/experiments/README.md
- Demo credentials are intentionally simple and non-production.
- Keep lockfiles committed where present.
- Do not commit generated artifacts (
node_modules, logs, large tar files). - Many compose files use bind mounts with
:z; this is intentional for SELinux-enabled systems to avoid permission-denied mount issues.
Use this baseline flow if you are new to Podman:
- Check tooling:
podman --versionpodman compose version(or installpodman-compose)
- Build and run a lab:
podman compose up --build
- Inspect running containers:
podman pspodman compose logs -f <service>
- Stop and clean:
podman compose down
For SELinux-enabled hosts, keep :z on bind mounts so container processes can
access mounted directories correctly.
Start with one lab improvement at a time:
- Clarify one architecture section
- Add one failure scenario
- Add one experiment result