English | 🇯🇵 日本語
.
├── .github/workflows/ # CI: auto-label, deploy trigger, reusable builders
├── clusters/{environment}/ # Flux CD sources (Kustomization, ImagePolicy)
├── docs/ # Architecture & access policy
├── proto/{product}/ # gRPC contracts shared between the product's services
└── {product}/
└── {service}/
├── infrastructure/ # Terragrunt stacks
├── kubernetes/ # Kustomize base & overlays
└── README.md # Service-specific notes
Cluster bootstrap, shared platform components, and the OIDC IAM that CI assumes live in panicboat/platform. Bring up the platform before targeting the cluster from this repo.
graph LR
User[User - Browser] -- "1. HTTPS" --> ALB[AWS ALB<br>application IngressGroup]
subgraph "Kubernetes Cluster"
ALB -- "2. hostNetwork :8080" --> Envoy[cilium-envoy]
Envoy -- "3. HTTPRoute via cilium-gateway" --> FrontendPod[Frontend Pod<br>dystopia/frontend]
FrontendPod -- "4. gRPC" --> MonolithPod[Monolith Pod<br>dystopia/monolith]
MonolithPod -- "5. PostgreSQL" --> RDS[(AWS RDS)]
end
Service-internal architecture is documented in each dystopia/<service>/README.md and in docs/ARCHITECTURE.md.
A PR-label / push-driven CI pipeline produces container images; Flux pulls them from GHCR into the cluster. release-please owns service versioning, so a production deploy is pinned to a semver tag rather than to a moving target.
flowchart LR
PR[PR / push main] --> Resolver[label-resolver]
Resolver -->|stack: container| Builder[container-builder]
Resolver -->|stack: terragrunt| Terragrunt[terragrunt-executor<br/>plan on PR / apply on main]
Resolver -->|stack: kubernetes| Diff[kubernetes diff<br/>PR comment]
Builder --> GHCR[(ghcr.io/panicboat/monorepo)]
Terragrunt --> AWS[(AWS)]
GHCR --> Flux[Flux CD]
Main[Commit on main] --> Flux
Flux --> K8s[(Kubernetes)]
auto-label--label-dispatcher.yamlrunslabel-dispatcheron every PR update and puts adeploy:<service>label on the PR for each service the diff touches.auto-label--deploy-trigger.yamlruns when a PR is labeled and on every push tomain.label-resolverreadsstacksinworkflow-config.yamland turns the labels into deploy targets.- Each target goes to the reusable workflow of its stack.
The paths each stack covers are defined in stacks in workflow-config.yaml.
| Stack | Workflow | On a PR | On push to main |
|---|---|---|---|
container |
reusable--container-builder.yaml |
Build and push to GHCR | Build and push to GHCR |
terragrunt |
reusable--terragrunt-executor.yaml |
terragrunt plan |
terragrunt apply |
kubernetes |
reusable--kubernetes-builder.yaml |
Kustomize diff as a PR comment | Nothing. Flux applies; CI never runs kubectl apply |
release.ymlruns release-please once for everyrelease-please-config.json(e.g.dystopia/monolith/release-please-config.json) and raises one release PR per service.- Merging a release PR publishes the release
<service>-vX.Y.Z. auto-release--trigger.yamlbuilds the container for that release and tags the imagevX.Y.Z.
docs/RELEASE_PLEASE.md explains why each service has its own config.
Each service has a directory under clusters/<environment>/ (e.g. clusters/production/dystopia/monolith).
image-policy.yamlselects the highestvX.Y.Ztag on GHCR.image-automation.yamlcommits that tag into the service's overlay (e.g.dystopia/monolith/kubernetes/overlays/production), so what runs in the cluster is what is checked in.
- panicboat/platform — cluster bootstrap, shared components, OIDC IAM.
- panicboat/deploy-actions — GitHub Actions that turn the changes in a PR into deploy labels and the labels into deploy targets.