Files
Jean-Gabriel Gill-Couture 3a6006846b
All checks were successful
Run Check Script / check (push) Successful in 1m52s
Compile and package harmony_composer / package_harmony_composer (push) Successful in 8m3s
docs: align roadmap and review workflow
2026-07-14 23:52:04 -04:00

80 lines
3.6 KiB
Markdown

# Deploy a Java + React app from its `docker-compose.yml`
Deploys an existing containerized app to Kubernetes with Harmony by
**importing its `docker-compose.yml`** — no hand-written manifests, no Argo.
The compose file stays the source of truth for the app's *base* shape;
deploy-only concerns are typed Rust.
```
app/ the "customer" project (their existing repo)
docker-compose.yml ← source of truth: images, ports, env, volumes
backend/ (Java + SQLite, Dockerfile)
frontend/ (React + nginx, Dockerfile)
Harmony.toml identity only — the app name (ADR-026 §3)
.harmony/contexts.toml deploy targets (local / prod), checked in
src/
compose.rs import docker-compose → typed model (loud on the unsupported)
chart.rs model + profile knobs → a hydrated helm chart (typed k8s)
publish.rs build images + (push to a registry | import to k3d)
score.rs ComposeAppScore: helm upgrade --install + Ingress
deploy.rs ComposeDeploy — the declarative builder (impls HarmonyApp)
main.rs the whole app, declared
```
## The app, declared (`main.rs`)
```rust
let app = ComposeDeploy::from_dir("timesheet", "./app")?
.expose("frontend", "timesheet.example.harmony.mcd")
.with(Postgres::managed()); // a managed CNPG database, wired in
harmony_cli::app::app_main(app).await
```
That declaration gets you `ship` / `deploy` / `status` / `logs` over any
`--context`, all from `harmony_app`. See
[Application CLI](../../docs/guides/application-cli.md) and
[Application Capabilities](../../docs/guides/application-capabilities.md).
## Design
- **compose = base, profile = deploy knobs.** The importer reads only images,
ports, env, volumes. Replicas, storage class, TLS, rolling strategy come
from the `Profile` the context carries (ADR-026 §6/§7), not from compose or
`Harmony.toml`. A behavioral knob in either is a defect.
- **Capabilities compose upward.** `.with(Postgres::managed())` /
`.with(Monitoring::new()...)` / `.with(ZitadelAuth::oidc(...))` each deploy
their own Scores and wire into the app **by reference** (a DB URL via
`secretKeyRef`, a client_id via `configMapKeyRef`) — no `ApplicationScore`,
no ArgoCD.
- **Same definition, local → prod.** Only the context changes; the *same*
`ComposeAppScore` converges on local k3d or a tenant cluster.
- **Loud, never lossy.** Bind mounts and unparseable ports are hard errors;
`depends_on`/`command`/`restart` are warned, never silently dropped.
## Run it (local k3d)
```sh
k3d cluster create compose-local --servers 1 --wait
kubectl --context k3d-compose-local create namespace timesheet
cd examples/compose_java_react # so the CLI finds .harmony/contexts.toml
cargo run --bin compose-deploy -- ship --context local # build → k3d import → deploy (+ Postgres)
cargo run --bin compose-deploy -- status --context local
cargo run --bin compose-deploy -- logs --context local --tail 50
```
Running from elsewhere? Point at the contexts file with
`--config <path>/.harmony/contexts.toml` (a context is always required — there
is no default, so you never hit the wrong cluster).
**Production** uses the same verbs against a prod context, with cluster
credentials brokered from OpenBao.
## Storage note (SQLite)
`Profile::Local` deploys single-replica with `Recreate` on a `ReadWriteOnce`
volume — safe for the demo's single-writer SQLite (one pod at a time). Prod
(`Profile::Prod`) replicates with `RollingUpdate` on `ReadWriteMany`; for a
real multi-writer store use `.with(Postgres::managed())` (the demo already
provisions it) and point the app at `DATABASE_URL`.