keel.yaml contains a deployment named
cloud-gcp. Run it with Keel Dev, fill in the form, press Deploy, and
ten minutes later you have a Keel Cloud with automatic HTTPS, nightly
backups, and IAP-only SSH. It is both a real installation path and a
worked example of what a serious keel.yaml looks like.
Intended scale: one company and a few of its clients — a handful of
organizations, tens of users, a few deploys a day. The design optimizes for
cost and operational simplicity, not horizontal scale.
Architecture
- caddy terminates TLS with automatic Let’s Encrypt certificates.
- A domain is optional. Leave Domain empty and Terraform derives
https://<ip-with-dashes>.sslip.iofrom the static IP, live with a real certificate as soon as the VM is up. Set the domain later and deploy again to switch; the VM is replaced (about two minutes), data and IP survive. - The data disk is separate from the VM. Postgres data, checkouts, and
certificates live on
/mnt/keel-data. The VM is disposable: when its cloud-init changes, Terraform replaces it while the disk and IP stay. - Images build on Cloud Build, because Keel’s own steps run in a container without a Docker daemon. The free tier covers it.
- SSH only through IAP. Port 22 is never open to the internet.
- Secrets never touch instance metadata. The database password and the at-rest encryption key sit in Secret Manager; the VM fetches them at first boot with its own identity.
- Backups outlive everything. Nightly
pg_dumpfiles go to a GCS bucket Terraform does not own, so destroy cannot delete them; the destroy path takes one final backup first. - Disk hygiene is automated: weekly
docker system prune, Artifact Registry keeps the ten most recent images, container logs are capped.
Cost
Approximate monthly cost inus-central1:
Very light use fits on
e2-small (≈ $22); heavy Terraform runs by clients
may want `e2-standard-2` (≈ $60). Change the machine type in the target’s
variables and deploy — it applies with a short restart.
First deploy
1
Prepare the project
Create a dedicated GCP project with billing enabled. Create a
keel-deployer service account, grant it Owner on that project, and
download a JSON key. A least-privilege role list is below if Owner is not
acceptable.2
Open the deployment
Clone the Keel repository, run
keel dev in it, and open the
cloud-gcp deployment. Create a target, for example production.3
Fill in the form
The variables are grouped into Google Cloud, Application, and
Sizing & retention. Every field documents itself, and required ones are
marked. Export variable manifest produces a document for whoever
supplies the values. Generate the encryption key and database password
with
openssl rand -hex 32 and keep them somewhere safe.4
Deploy
Press Deploy now, keep Action: Deploy and Mode: Full. The first
run takes about ten minutes (API enablement plus VM boot) and ends with
the Keel Cloud URL and Server IP outputs. Pick Plan in the modal
to preview Terraform changes without applying anything.
5
Point DNS, if you set a domain
Create an A record for the domain pointing at the Server IP. HTTPS
goes live a couple of minutes after DNS resolves; no second deploy is
needed. The
SITE_STATUS output says where things stand. With no domain
the sslip.io URL is already live.6
Sign in
Open the URL, create the first account, and invite the team from the
Members page.
Operations
-
Restore a backup (
gs://<project>-keel-backups/pg/*.sql.gz): -
Logs and status on the server:
sudo docker logs keel-cloud,sudo docker ps,systemctl list-timers keel-*. -
Resize disks: raise Boot disk or Data disk, deploy, then grow the
filesystem (
sudo resize2fs /dev/disk/by-id/google-keel-datafor the data disk). -
Rotate the database password: update the variable, deploy, then
ALTER USER keel WITH PASSWORD '…'inside Postgres andsudo rebootso the VM re-reads its secrets. Rotating the encryption key is not supported. -
Destroy: Deploy → Destroy the infrastructure → type
destroy. A final backup is taken; the VM, disks, IP, network, registry, and secrets are removed. The backup and state buckets remain. -
Multiple environments: each target keeps its own Terraform state under
a
keel-cloud/<key>prefix, so astagingtarget with its own project coexists withproduction. The key is the target’s stable ID, so renaming a target is safe; targets created before ID-keyed state (under the target name) are found automatically. -
Adopting existing state: if the run finds a Keel Cloud in the project
but no state for this target, it stops rather than build a second one and
lists the
keel-cloud/…keys it found. Set Terraform state key (under Sizing & retention) to the right one and deploy again.
Least-privilege deployer
Instead of Owner, grant the deployer service account:roles/compute.admin,
roles/iam.serviceAccountAdmin, roles/iam.serviceAccountUser,
roles/resourcemanager.projectIamAdmin, roles/artifactregistry.admin,
roles/secretmanager.admin, roles/storage.admin,
roles/cloudbuild.builds.editor, roles/serviceusage.serviceUsageAdmin,
roles/compute.osAdminLogin, and roles/iap.tunnelResourceAccessor.
Adding the GitHub App
Push-triggered deploys need the GitHub App variables on the server. Add them to/opt/keel/secrets.env on the VM (KEEL_GITHUB_APP_ID=… and the
rest, see GitHub App) and apply with: