Context: Personal repo (docker_multilang_project) where I containerized a Node API and a Python utility. No clients, no prod traffic—just me trying to stop “works on my machine” excuses.
AI assist: ChatGPT summarized Docker docs and suggested health-check patterns; I kept all final code/tests/manual notes in the repo.
Status: Student exercise. Useful for onboarding and demos, but not hardened for production.

Reality snapshot

  • Stack: Node 20 + Express (/api), Python 3.11 helper service, Postgres 15. Managed via Docker Compose v3.9.
  • Use cases: practice dependency isolation, log correlation, and failure drills.
  • Observability: JSON logs, /healthz endpoints, and docker compose logs. No ELK/Splunk stack yet.
  • Hosting: Runs locally or on a single VM. Not deployed for real users.

Architecture overview

docker_multilang_project/
├── docker-compose.yml
├── node-service/
│ ├── Dockerfile
│ ├── package.json
│ └── server.js
├── python-service/
│ ├── Dockerfile
│ └── app.py
└── scripts/
└── smoke.sh
  • node-service: REST API that exposes math endpoints and proxies to the Python service when needed.
  • python-service: Handles CPU-heavy calculations/string parsing. Communicates via HTTP on the internal network.
  • scripts/smoke.sh: Quick smoke test hitting both services + Postgres after docker compose up.

Compose highlights

services:
api:
build: ./node-service
env_file: .env.api
depends_on: [db]
ports: ["4000:4000"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4000/healthz"]
interval: 30s
timeout: 5s
retries: 3
python:
build: ./python-service
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
  • Why: Health checks keep Compose from routing traffic to sick containers. Named volumes preserve data. .env keeps secrets out of Git.
  • Observability: Logs include traceId so I can follow a request from Node → Python. When I tail logs, it’s clear which container said what.

Experiments & results

ExperimentObservationOutcome
Remove Express dependency mid-runOnly the Node build failed; Python kept serving other requestsProof that isolation works.
Force Python infinite loopNode flagged the dependency timeout but stayed healthyNeed better circuit breakers, but UI didn’t crash.
Mount log volume (./logs:/app/logs)Restarting containers kept historical logsGood for debugging; README now reminds me to clean the directory.
Scale Node tier (docker compose up --scale api=3)Load balanced across three containers (still localhost traffic)Helps me explain horizontal scaling during interviews.

Tooling & docs

  • Scripts: scripts/new-cert.sh, scripts/smoke.sh, and scripts/cleanup.sh keep routines consistent.
  • Documentation: README.md covers setup, health checks, and TODOs; docs/runbook.md logs drills (failures, fixes, lessons).
  • Security: Multi-stage builds, npm audit, and pip-audit run before merges. Findings get tracked in repo issues.
  • CI/CD: GitHub Actions builds the images, runs lint/tests, and publishes artifacts so I can pull them on another machine.

What still needs work

  • CI smoke tests that spin up the stack in GitHub Actions and run scripts/smoke.sh. Currently manual.
  • More realistic observability stack (Prometheus + Grafana or at least Loki) instead of plain logs.
  • Automated cleanup for volumes/networks so teammates don’t end up with stale resources.
  • Better story around secrets (maybe doppler/.env vault). For now I rely on .env.example files.

Repro steps (for future me or teammates)

  1. Copy .env.example to .env.api and .env.db; fill in passwords.
  2. Run docker compose up --build. Wait for health checks to go green.
  3. Execute scripts/smoke.sh to hit Node, Python, and Postgres.
  4. Tail logs with docker compose logs -f api python and confirm traceId flows across services.
  5. Kill one service (docker compose kill python) and verify API degrades gracefully.

Failure drills and lessons

  • Breaking Postgres: Stopped the DB to see error handling. API returned 503 with a user-friendly message—logged the stack trace server-side only.
  • Timeouts between services: Added a 2s timeout to the API→Python call; the frontend shows a banner when dependencies are slow.
  • Log file bloat: Mounted a host directory and hit 1GB fast. Added log rotation config + a cleanup script.
  • Dependency sprawl: npm/pip upgrades broke builds; pinned versions and added npm ci/pip install -r in the Dockerfiles.

How I use this in interviews

  • Talk through why I separated responsibilities (Node for routing/auth, Python for CPU tasks).
  • Show the scripts/ folder as proof I operationalize even small labs.
  • Admit gaps: no real tracing stack, no secrets manager, and only local/VM deployments so far.
  • Offer to screen share the runbook so interviewers see the habit of documenting drills.

Next iterations

  • Add k6 tests to simulate basic load across both services.
  • Replace raw curl health checks with a tiny status page showing dependency health.
  • Publish a short Loom walkthrough of the repo structure and the smoke test output.
  • Try the same pattern with a queue (e.g., RabbitMQ) to decouple CPU-heavy tasks.

Links