Install
Thirty seconds to a running product with a fabricated estate, then the same command for a real one.
Access, today. The product is pre-release and self-hosted, and the repository and image are private until the first pilot. The quickstart below works once you have been granted access to the repository; ask through the contact on the site, say which AWS estate you would evaluate it against, and you will get access and a trial licence. Nothing on this page phones home and nothing is collected until you connect an account.
Quickstart
You need Docker (with Compose v2) and nothing else.
git clone https://github.com/dawidper/ccc && cd ccc/observability
SEED_DEMO=true make compose-up # Postgres + every role, migrated, seeded
docker compose -f deploy/docker-compose.yml logs seed # the demo admin's password, printed once
Then open http://127.0.0.1:18080 and sign in as admin@demo.invalid with
that password. The public status page — no login — is
http://127.0.0.1:18080/status/demo.
The demo tenant contains three fabricated accounts, 200 EC2 instances,
Kubernetes nodes and pods, and thirty days of operational history. It also
includes four tag-defined applications and teams, sixty days of spend,
budgets, anomalies, recommendations, Kubernetes cost allocation, and
provisioning templates, requests, approvals, simulated logs and drift.
Account IDs start at 000000000001 and example names use demo-.
The seed makes no AWS calls, executes no provisioning and sends no
notifications. The example agent is revoked and the webhook is disabled.
Re-running the seed upgrades an inventory-only demo and preserves its login.
make compose-up generates deploy/.env with random passwords the first time
it runs; no credential is committed and there is no default password anywhere,
the demo admin's included. /readyz answers 200 about ten seconds after the
image is built. The host ports are 18080 and 55432 rather than 8080 and 5432
because the other pillars of this workspace hold the obvious ones; override
with SERVE_PORT and POSTGRES_PORT.
make compose-logs # follow every role
make seed # (re)seed the demo against the running stack; RESET=1 replaces it
make compose-down # stop; WIPE=1 also deletes the database volume
Without SEED_DEMO=true the same command brings up an empty, real stack. Its
first tenant is created with panorama admin bootstrap, which prints the
administrator's generated password once:
docker compose -f deploy/docker-compose.yml run --rm serve admin bootstrap \
--tenant-slug acme --tenant-name 'Acme' --email you@example.com
From there, people are added, reset and removed without SQL — the portal's
Users page, or panorama admin create-user, reset-password, set-role,
disable-user, enable-user and revoke-sessions (docs/runbook.md,
"Onboarding and removing people").
Kubernetes
The same image runs each role as its own Deployment, with migrate as a Job before them, against a Postgres you provide — the compose shape, productionised. The manifests are observability/deploy/k8s, a kustomization whose image names no usable tag: you deploy a copy of observability/deploy/overlays/example, which pins the image by digest, and add the namespace and the components you use — eks-irsa for IRSA on EKS, external-secrets to fill the Secret from your secret store.
What it renders
configmap.yamlmigrate-job.yamlserve.yamlschedule.yamlwork.yamlnotify.yamlcleanup.yamlserviceaccounts.yamlpdb.yamlnetworkpolicy.yaml
The Secret
Named panorama-secrets, with exactly these keys — three database URLs for three database roles, and the worker fleet's credential:
CONTROL_DATABASE_URLDATABASE_URLMIGRATE_DATABASE_URLWORKER_TOKEN
Apply
kubectl create secret generic panorama-secrets --from-literal=DATABASE_URL=... \
--from-literal=CONTROL_DATABASE_URL=... --from-literal=MIGRATE_DATABASE_URL=... \
--from-literal=WORKER_TOKEN=placeholder
observability/deploy/k8s/deploy.sh <your overlay> <namespace> # serve waits for a credential
kubectl exec deploy/panorama-work -- /panorama admin create-worker-token --name 'cluster workers' --raw
# put the token in the Secret's WORKER_TOKEN, then:
observability/deploy/k8s/deploy.sh <your overlay> <namespace>
On a first install the worker token cannot exist yet: minting it needs the migrated schema, which deploy.sh creates, and serve refuses to start until a credential exists. So the first deploy names serve and exits 1 with every other role up; the token is minted from a running work pod (--raw prints it alone), and the second deploy, with the Secret's new version, rolls every role. deploy.sh deploys in two phases: it recreates and applies the migrate Job on its own, waits for it to complete — a failed migration stops there, with the roles untouched — and only then applies the roles and waits for every Deployment's rollout, naming, describing and logging any role that did not get there before it exits 1; one kubectl apply -k would roll new binaries out regardless of the schema and could not update a finished Job's immutable template on the next release. Each pod template carries a digest of the ConfigMaps and the Secrets' versions, so a release that only changes configuration, or only rotates the Secret, rolls the pods that read it — rotating a credential is running deploy.sh again.
Before the first apply, fill TRUSTED_PROXIES in the ConfigMap with your ingress controller's addresses — serve refuses to start while it is empty, because a proxy header trusted from anywhere would let any pod choose the login throttle's key — and run bootstrap-roles.sql once as the database owner. What is deliberately not in the manifests: an Ingress (TLS terminates at your edge), the database, autoscaling, and the hosted track's environments.
The provisioning agent
Provisioning runs in your account, never in ours: a small agent you install where your infrastructure credentials already are. Its own page covers install, configuration, verification and upgrade.
Then
Connect a real AWS account, install the licence you were issued, and keep the runbook to hand.