{"id":35790346,"url":"https://github.com/johnjaysonlpz/docker-polyglot-lab","last_synced_at":"2026-02-10T10:01:11.242Z","repository":{"id":329093193,"uuid":"1109824666","full_name":"johnjaysonlpz/docker-polyglot-lab","owner":"johnjaysonlpz","description":"Polyglot microservice lab (Gin, Spring Boot, Django) showcasing Docker, Prometheus, Grafana, and production-style observability patterns.","archived":false,"fork":false,"pushed_at":"2026-01-30T10:56:34.000Z","size":304,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-31T02:59:43.675Z","etag":null,"topics":["devops","django","docker","docker-compose","gin","github-actions","go","grafana","grafana-dashboard","java","microservices","observability","prometheus","python","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnjaysonlpz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-12-04T10:34:02.000Z","updated_at":"2026-01-07T04:54:20.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/johnjaysonlpz/docker-polyglot-lab","commit_stats":null,"previous_names":["johnjaysonlpz/docker-polyglot-lab"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/johnjaysonlpz/docker-polyglot-lab","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnjaysonlpz%2Fdocker-polyglot-lab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnjaysonlpz%2Fdocker-polyglot-lab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnjaysonlpz%2Fdocker-polyglot-lab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnjaysonlpz%2Fdocker-polyglot-lab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnjaysonlpz","download_url":"https://codeload.github.com/johnjaysonlpz/docker-polyglot-lab/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnjaysonlpz%2Fdocker-polyglot-lab/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29117916,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-05T05:31:32.482Z","status":"ssl_error","status_checked_at":"2026-02-05T05:31:29.075Z","response_time":65,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["devops","django","docker","docker-compose","gin","github-actions","go","grafana","grafana-dashboard","java","microservices","observability","prometheus","python","spring-boot"],"created_at":"2026-01-07T08:08:01.608Z","updated_at":"2026-02-05T09:01:29.213Z","avatar_url":"https://github.com/johnjaysonlpz.png","language":"Java","readme":"# Docker Polyglot Lab\n\n[![CI](https://github.com/johnjaysonlpz/docker-polyglot-lab/actions/workflows/cicd.yaml/badge.svg)](https://github.com/johnjaysonlpz/docker-polyglot-lab/actions/workflows/cicd.yaml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![Release](https://img.shields.io/github/v/release/johnjaysonlpz/docker-polyglot-lab)](https://github.com/johnjaysonlpz/docker-polyglot-lab/releases)\n[![Last Commit](https://img.shields.io/github/last-commit/johnjaysonlpz/docker-polyglot-lab)](https://github.com/johnjaysonlpz/docker-polyglot-lab/commits)\n\n![Go](https://img.shields.io/badge/Go-1.25.6-informational)\n![Gin](https://img.shields.io/badge/Gin-v1.11.0-informational)\n![Java](https://img.shields.io/badge/Java-25.0.2-informational)\n![Spring%20Boot](https://img.shields.io/badge/Spring%20Boot-4.0.2-informational)\n![Python](https://img.shields.io/badge/Python-3.12-informational)\n![Django](https://img.shields.io/badge/Django-6.0.2-informational)\n![Docker](https://img.shields.io/badge/Docker-Compose-informational)\n![Observability](https://img.shields.io/badge/Observability-Alloy%20%7C%20Prometheus%20%7C%20Loki%20%7C%20Tempo%20%7C%20Grafana-informational)\n\n[![Docker Pulls](https://img.shields.io/docker/pulls/johnjaysonlopez/golang-gin-app)](https://hub.docker.com/r/johnjaysonlopez/golang-gin-app)\n[![Docker Pulls](https://img.shields.io/docker/pulls/johnjaysonlopez/java-springboot-app)](https://hub.docker.com/r/johnjaysonlopez/java-springboot-app)\n[![Docker Pulls](https://img.shields.io/docker/pulls/johnjaysonlopez/python-django-app)](https://hub.docker.com/r/johnjaysonlopez/python-django-app)\n\n\nA **polyglot microservices + observability** lab built to showcase **modern microservice best practices** and a complete **metrics / logs / traces** pipeline you can run locally — with a structure that also maps cleanly to **cloud production patterns**.\n\nIt includes three production-minded HTTP services:\n- **Go / Gin** (`golang-gin/`)\n- **Java / Spring Boot** (`java-springboot/`)\n- **Python / Django** (`python-django/`)\n\n…and a full observability stack:\n- **Alloy** (OpenTelemetry ingest + Docker log shipping)\n- **Prometheus + Alertmanager** (metrics + alerting)\n- **Loki** (logs)\n- **Tempo** (traces + metrics-generator)\n- **Grafana** (dashboards + Explore)\n\nThe canonical “run everything” entrypoint is the **Docker Compose stacks** in [`docker/`](docker/README.md).\n\n---\n\n## TL;DR\n\n### Run the full stack (pull images)\n\n#### up (NO secrets)\n```bash\nAPP_ENV=staging APP_VERSION=2.0.0 \\\nREGISTRY=docker.io/johnjaysonlopez \\\ndocker compose --project-directory docker \\\n  -p polyglot-lab-staging -f docker/compose.staging.nosecrets.yaml \\\n  up --pull always --remove-orphans\n```\n\nOpen:\n- Go (Gin): `http://127.0.0.1:8081`\n- Java (Spring Boot): `http://127.0.0.1:8082`\n- Python (Django): `http://127.0.0.1:8083`\n- Grafana: `http://127.0.0.1:3000`\n\n### Generate traffic (to light up dashboards)\n\nIf the system is idle, generate a little traffic so **metrics/logs/traces** populate quickly:\n\n```bash\nTARGETS=(\n  \"svc-a root (200)|http://127.0.0.1:8081/\"\n  \"svc-b root (200)|http://127.0.0.1:8082/\"\n  \"svc-c root (200)|http://127.0.0.1:8083/\"\n\n  \"svc-a info (200)|http://127.0.0.1:8081/info\"\n  \"svc-b info (200)|http://127.0.0.1:8082/info\"\n  \"svc-c info (200)|http://127.0.0.1:8083/info\"\n\n  \"svc-a WRONG /nope (404)|http://127.0.0.1:8081/nope\"\n  \"svc-b WRONG /nope (404)|http://127.0.0.1:8082/nope\"\n  \"svc-c WRONG /nope (404)|http://127.0.0.1:8083/nope\"\n\n  \"svc-a WRONG /infoo (404)|http://127.0.0.1:8081/infoo\"\n  \"svc-b WRONG /infoo (404)|http://127.0.0.1:8082/infoo\"\n  \"svc-c WRONG /infoo (404)|http://127.0.0.1:8083/infoo\"\n)\n\nREQUESTS_PER_TARGET=25\n\nfor entry in \"${TARGETS[@]}\"; do\n  IFS='|' read -r label url \u003c\u003c\u003c \"$entry\"\n  echo \"Hitting: $label -\u003e $url x$REQUESTS_PER_TARGET\"\n  for ((i=1; i\u003c=REQUESTS_PER_TARGET; i++)); do\n    code=\"$(curl -sS -o /dev/null -w '%{http_code}' \"$url\" || echo '000')\"\n    echo \"  [$i/$REQUESTS_PER_TARGET] HTTP $code\"\n  done\ndone\n```\n\nThen check:\n- **Prometheus** targets: `http://127.0.0.1:9090/targets`\n- **Grafana**: `http://127.0.0.1:3000` → Dashboards/Explore (Prometheus/Loki/Tempo)\n\n\u003e For **staging pulls**, **secrets-enabled** stacks, and the full operational guide, see [`docker/README.md`](docker/README.md).\n\n---\n\n## Highlights\n\n### Consistent service contract across Go/Gin, Java/Spring Boot, Python/Django\n\nAll three services expose the same HTTP surface area:\n- `GET /`        — banner (“service is running”)\n- `GET /info`    — build/service metadata\n- `GET /health`  — liveness probe\n- `GET /ready`   — readiness probe (used by **Docker Compose** healthchecks)\n- `GET /metrics` — Prometheus metrics (text format)\n\nThis consistency is intentional: it keeps health checks, scraping, dashboards, and alerts predictable in a polyglot stack.\n\n### What this repo is designed to demonstrate\n\n- **Production-minded containerization (real-world patterns)**\n  - multi-stage Dockerfiles\n  - build metadata injection (`SERVICE_NAME`, `VERSION`, `BUILD_TIME`)\n  - apps run as **non-root** users (unprivileged UID/GID)\n  - slim runtime stages to reduce attack surface\n\n- **Concrete production-hardening behaviors (operator-facing)**\n  - **Timeouts + graceful shutdown semantics** are explicitly configured and validated per service (server timeouts, shutdown timeouts, clean termination on `SIGTERM`).\n  - **Payload limits** are enforced at the edge of each service (request body sizing / upload limits) with clear 413 behavior.\n  - **Trusted proxy controls** (`TRUSTED_PROXIES` / Tomcat RemoteIpValve settings) prevent spoofed client IPs and ensure correct `X-Forwarded-*` handling.\n  - **Non-root runtime**: app containers run as unprivileged UID/GID; hardening is reinforced via Compose (cap drops, tmpfs/read-only patterns where applicable).\n  - **Build provenance**: images embed build metadata (`SERVICE_NAME`, `VERSION`, `BUILD_TIME`) surfaced via `GET /info` and exported via metrics labels where applicable.\n  - **CI gates + reproducibility**: local CI parity runner (`.ci-local.sh`) + pinned toolchain versions (`.ci-tool-versions.sh`) to minimize “works on my machine” drift.\n  - **Security scanning** is part of the normal workflow (language-specific vuln/dependency scanning + CI enforcement).\n\n- **Composable environments with strong local parity**\n  - **development** (apps only; fastest loop)\n  - **integration** (apps + full observability)\n  - **staging (prod-like)** stacks that support **registry pulls** (build once, deploy many)\n  - modular Compose using `include:` building blocks\n\n- **Operational correctness and hardening where it belongs**\n  - Runtime hardening and healthchecks are enforced via **Compose** (cap drops, tmpfs/read-only patterns where applicable; /ready healthcheck), not embedded in the Dockerfile.\n  - a shared `toolbox-init` pattern stages a tiny BusyBox helper for healthchecks without bloating app images\n  - secrets-enabled stacks use overlays + a safe bootstrap workflow\n\n- **Observability you can trust**\n  - **metrics**: Prometheus scraping across all services\n  - **logs**: JSON logs to stdout/stderr, shipped via Alloy → Loki\n  - **traces**: OTLP → Alloy → Tempo, queryable in Grafana\n  - stable metrics labels to avoid cardinality blowups (e.g., `**path=\"__unmatched__\"**` for 404s; template paths for labels)\n\n- **High-signal CI/CD with local parity**\n  - `.ci-local.sh` mirrors the CI intent locally\n  - `.ci-tool-versions.sh` pins tool versions for reproducibility\n  - security scanning + dependency checks are integrated\n  - strong test + coverage expectations (designed to detect drift aggressively)\n\n---\n\n## System architecture (full stack)\n\n\u003e Scope: this diagram represents the **full stack** (integration/staging) runs where observability is enabled. The **apps-only** development stack does not start Prometheus/Grafana/Loki/Tempo/Alertmanager/Alloy.\n\n```text\nUser/Operator\n   |\n   |  HTTP (host ports; bound to 127.0.0.1)\n   v\nHost Ports\n   |---- 127.0.0.1:8081 --------\u003e golang-gin-app (container :8080)\n   |---- 127.0.0.1:8082 --------\u003e java-springboot-app (container :8080)\n   |---- 127.0.0.1:8083 --------\u003e python-django-app (container :8080)\n   |\n   |---- 127.0.0.1:3000 --------\u003e Grafana\n   |---- 127.0.0.1:9090 --------\u003e Prometheus\n   |---- 127.0.0.1:9093 --------\u003e Alertmanager\n   |---- 127.0.0.1:3200 --------\u003e Tempo query/frontend\n   |---- 127.0.0.1:3100 --------\u003e Loki\n   |---- 127.0.0.1:4317 --------\u003e Alloy (OTLP/gRPC ingest)\n   |---- 127.0.0.1:4318 --------\u003e Alloy (OTLP/HTTP ingest; POST /v1/traces)\n   |---- 127.0.0.1:12345 -------\u003e Alloy UI/status\n\nMETRICS:\nPrometheus ---scrape /metrics---\u003e Go/Gin app\nPrometheus ---scrape /metrics---\u003e Java/Spring Boot app\nPrometheus ---scrape /metrics---\u003e Python/Django app\nTempo --metrics-generator remote_write--\u003e Prometheus\n\nTRACES:\nGo/Gin app ------------ OTLP ----\\\nJava/Spring Boot app -- OTLP -----+--\u003e Alloy --\u003e Tempo (trace store) \u003c-- Tempo query/frontend\nPython/Django app------ OTLP ----/\n\nLOGS:\nDocker daemon (container logs) --\u003e Alloy --\u003e Loki\n\nDASHBOARDS:\nGrafana --\u003e Prometheus (metrics)\nGrafana --\u003e Loki (logs)\nGrafana --\u003e Tempo query/frontend (traces)\n\nALERTING:\nPrometheus --\u003e Alertmanager --\u003e (optional) Telegram / other receivers\n\nHEALTHCHECKS:\ntoolbox-init (busybox to shared volume) --\u003e healthchecks --\u003e services (apps, Prom, AM, TempoQ, Grafana)\n```\n\nInit helpers (run inside the stack):\n- `toolbox-init` — stages BusyBox into the shared `toolbox` volume for healthchecks\n- `loki-init` — prepares Loki runtime directories/permissions for the volume-backed store\n- `tempo-init` — prepares Tempo runtime directories/permissions for the volume-backed store\n\nFor a deeper breakdown (compose entrypoints, overlays, healthchecks, and secrets workflow), see [`docker/README.md`](docker/README.md).\n\n---\n\n## Module documentation\n\n- [`docker/README.md`](docker/README.md) — Compose stacks, overlays, staging pulls, and observability wiring\n- [`golang-gin/README.md`](golang-gin/README.md) — Go service API + observability contract + container image details\n- [`java-springboot/README.md`](java-springboot/README.md) — Java service API + observability contract + operational knobs + container image details\n- [`python-django/README.md`](python-django/README.md) — Django service API + observability contract + operational knobs + container image details\n\n---\n\n## Why a hybrid observability approach\n\nThis repo intentionally uses the **best model per signal**, then correlates everything in Grafana:\n\n- **Metrics: Prometheus scrape model**\n  - `/metrics` scraping is simple, reliable, and scales well for RED/USE signals.\n  - it avoids pushing metrics through a collector unless you actually need that architecture.\n\n- **Traces: OTLP push model**\n  - traces are naturally push-based.\n  - services export OTLP to **Alloy**, which batches/routes, then forwards to **Tempo**.\n\n- **Logs: agent-based tailing**\n  - logs are emitted as JSON to stdout/stderr.\n  - **Alloy** tails container logs (via Docker) and ships them to **Loki** without per-app log agents.\n\nThe goal is pragmatic: **simple where possible, centralized where it pays off**, with a clean correlation story (request ID + trace/span IDs).\n\n---\n\n## Production template for cloud\n\nThis repo is structured so it can serve as a **production reference template**, not just a demo:\n\n- Replace Compose with **Kubernetes** (or ECS/Nomad) while keeping the same contracts:\n  - readiness/liveness probes map directly to `/ready` and `/health`\n  - Prometheus scrapes services (or use managed Prometheus + scrape configs)\n  - Alloy becomes a DaemonSet/sidecar/agent (or a managed collector)\n  - Tempo/Loki/Grafana can be self-hosted or swapped for managed equivalents\n\n- Keep the same “shape”:\n  - `/metrics` remains the metrics boundary\n  - OTLP remains the trace boundary\n  - JSON logs remain the log boundary (collected by platform logging)\n\n- **Staging “pull images”** already exists here (registry images + prod-like stack), mirroring how production deployments operate: **build once, deploy many**.\n\n---\n\n## Running the stack\n\nAll authoritative run commands (including repo-root staging pulls and secrets-enabled stacks) are documented in:\n- [`docker/README.md`](docker/README.md)\n\nCommon entrypoints:\n- **apps-only (local builds):** `docker/compose.development.yaml`\n- **apps + observability (no secrets overlays):** `docker/compose.integration.nosecrets.yaml`\n- **apps + observability (with secrets overlays):** `docker/compose.integration.yaml`\n- **staging pulls (no secrets / with secrets):** `docker/compose.staging*.yaml`\n\n---\n\n## Secrets and local bootstrap\n\n### `.bootstrap-local.sh` (secrets + permissions)\n\nIf you use the **secrets-enabled** Compose entrypoints:\n- `docker/compose.integration.yaml`\n- `docker/compose.staging.yaml`\n\n…run the bootstrap script once from the repo root. It creates the expected Docker secrets files in `docker/secrets/` and sets ownership/permissions so Grafana/Alertmanager can read them safely.\n\nRequired env vars:\n- `GRAFANA_ADMIN_USER`\n- `GRAFANA_ADMIN_PASSWORD`\n- `TELEGRAM_BOT_TOKEN`\n- `TELEGRAM_CHAT_ID`\n\nUsage (from repo root):\n```bash\nexport GRAFANA_ADMIN_USER=admin\nexport GRAFANA_ADMIN_PASSWORD='supersecret'\nexport TELEGRAM_BOT_TOKEN='...'\nexport TELEGRAM_CHAT_ID='...'\n\n./.bootstrap-local.sh\n```\n\n\u003e For the overlay mechanics and the exact secret file wiring, see [`docker/README.md`](docker/README.md).\n\n---\n\n## CI/CD\n\n### GitHub Actions workflow\n\nThe workflow runs on pushes, PRs, and tags; it enforces formatting, linting, tests, coverage expectations, and security scanning. It also builds app images and pushes them to a registry on release tags.\n\nSee: [`.github/workflows/cicd.yaml`](.github/workflows/cicd.yaml)\n\n### Local CI parity: `.ci-local.sh`\n\nUsage (from repo root):\n\n```bash\n./.ci-local.sh               # run all: go + java + python\n./.ci-local.sh go            # Go only\n./.ci-local.sh java          # Java only\n./.ci-local.sh python        # Python only\n./.ci-local.sh doctor all    # preflight checks (recommended)\n```\n\nTool pins live in: `.ci-tool-versions.sh`\n\n---\n\n## Repository structure\n\n| Path | Purpose |\n|---|---|\n| [`docker/`](docker/README.md) | Compose stacks (apps-only + full observability), configs for Alloy/Prometheus/Loki/Tempo/Grafana/Alertmanager, secret overlays, staging pulls |\n| [`golang-gin/`](golang-gin/README.md) | Go + Gin service |\n| [`java-springboot/`](java-springboot/README.md) | Spring Boot service |\n| [`python-django/`](python-django/README.md) | Django service |\n| [`.github/workflows/cicd.yaml`](.github/workflows/cicd.yaml) | CI/CD workflow |\n| [`.bootstrap-local.sh`](.bootstrap-local.sh) | Local bootstrap for secrets + permissions |\n| [`.ci-local.sh`](.ci-local.sh) | Local CI runner |\n| [`.ci-tool-versions.sh`](.ci-tool-versions.sh) | Tool/version pins used by local CI + CI |\n| [`.gitignore`](.gitignore) | Secret hygiene + build artifact ignores |\n\n---\n\n## Languages, frameworks, and stack versions\n\n### App runtimes/frameworks\n- **Go:** `1.25.6`\n- **Gin:** `v1.11.0`\n- **Java:** `25.0.2`\n- **Spring Boot:** `4.0.2`\n- **Python:** `3.12`\n- **Django:** `6.0.2`\n\n### Observability images (from `docker/compose._observability.yaml`)\n- **Alloy:** `grafana/alloy:v1.12.2`\n- **Prometheus:** `prom/prometheus:v3.9.1`\n- **Alertmanager:** `prom/alertmanager:v0.31.0`\n- **Loki:** `grafana/loki:3.6.4`\n- **Tempo:** `grafana/tempo:2.10.0`\n- **Grafana:** `grafana/grafana:12.3.2`\n\n---\n\n## `.gitignore` policy\n\nThis repo is designed to be safe-by-default for local development:\n- real `.env` files and secret outputs should **never** be committed\n- build artifacts and generated security reports should remain out of git\n\nSee [`.gitignore`](.gitignore) `for the exact ignore/allowlist rules.`\n\n---\n\n## Status\n\nThis is a personal lab project. There are **no stability or backwards-compatibility guarantees**:\ndirectory layout, APIs, and Docker tags may change at any time.\n\nUse it as a reference or template at your own risk.\n\n---\n\n## License\n\nMIT — see [`LICENSE`](LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnjaysonlpz%2Fdocker-polyglot-lab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnjaysonlpz%2Fdocker-polyglot-lab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnjaysonlpz%2Fdocker-polyglot-lab/lists"}