Skip to content

Part 5 — Deploy & verify

Everything’s wired. This part is the dress rehearsal: push, watch, verify, and learn the commands you’ll lean on when something goes sideways.

From part 2 you have a Reserved IP. At your DNS provider, add the A records you used in values/<env>.yaml:

dev.example.com. IN A 134.209.xx.xx
example.com. IN A 134.209.xx.xx # if values/prod.yaml uses the apex

Verify before pushing — ACME’s HTTP-01 challenge fails fast when DNS isn’t ready:

Terminal window
dig +short dev.example.com
# → 134.209.xx.xx

If you’ve followed part 4, every push to main deploys automatically. To kick off the first run without a code change:

Terminal window
git commit --allow-empty -m "ci: trigger first deploy"
git push origin main

Watch it live:

Terminal window
gh run watch

Or open the Actions tab in GitHub.

While CI is casting, on your laptop:

Terminal window
rune get services -n dev
# NAME NAMESPACE GEN DESIRED READY AGE
# web dev 1 1 1 45s
rune get instances -n dev
rune health web -n dev --checks
rune logs web -n dev --tail 50 --follow

The first cast pulls the image from GHCR — first pull on a fresh droplet can take 30-60s depending on image size. Subsequent rollouts are seconds.

If you used tls.mode: auto, watch the certificate get issued:

Terminal window
rune get ingresses -n dev
# NAME HOST TLS STATUS READY AGE
# web dev.example.com auto Issued true 90s

Then hit the URL:

Terminal window
curl -sI https://dev.example.com
# HTTP/2 200
# server: Caddy

If STATUS stays Pending for more than ~5 minutes, see Expose a service → Troubleshooting — usually it’s a DNS record that hasn’t propagated.

A typical change loop now looks like this:

Terminal window
# make a code change in apps/web/
git add apps/web/
git commit -m "web: tweak homepage"
git push

CI rebuilds + redeploys web only (because apps/web/** is the path glob in deploy-config.yml). The runner streams the rollout in the cast job; on your laptop:

Terminal window
rune get instances -n dev -w
# Watch instances cycle: old Terminating, new Running.

rune cast is idempotent — no diff, no work. Re-running it with the same inputs is a safe no-op.

6. The eight commands you’ll actually use

Section titled “6. The eight commands you’ll actually use”

Bookmark these. They cover ~95% of day-to-day operation:

CommandUse case
rune cast <file> -n <env>Apply a service / configmap / secret.
rune get services -n <env>List services + readiness.
rune get instances -n <env>The actual containers running.
rune logs <svc> -n <env> -fTail logs across all instances.
rune exec <svc> -n <env> shOpen a shell inside an instance.
rune restart <svc> -n <env>Roll instances (e.g. after secret update).
rune scale <svc> N -n <env>Quick scale without re-casting.
rune health <svc> -n <env> --checksWhy is a probe failing?

For everything else, CLI overview.

7. When CI is broken but you need to deploy

Section titled “7. When CI is broken but you need to deploy”

You can always cast manually from your laptop using the admin token from part 2:

Terminal window
rune login myapp-dev --server $(terraform -chdir=infra/terraform/do output -raw grpc_endpoint) \
--token-file infra/terraform/do/rune-admin.token \
--default-namespace dev
# Same templating CI does, just run locally:
rune cast infra/runeset/casts/web.yaml \
--values infra/runeset/values/dev.yaml \
--set app.tag=sha-$(git rev-parse --short HEAD) \
-n dev

8. When things break — first three places to look

Section titled “8. When things break — first three places to look”
  1. rune logs <svc> -n <env> --tail 200 — app-level errors. 80% of issues stop here.
  2. rune get instances -n <env> + rune get instance <id> -o yaml — restart count, last termination reason, image pull errors.
  3. ssh root@$RESERVED_IP 'journalctl -u runed -n 200 --no-pager' — server-level issues (registry auth, ACME, networking). The runed operations doc lists log markers.

For specific failure modes, the Errors reference maps exit codes and error messages to their fix.

Terminal window
# Drop everything in the namespace
rune delete services --all -n dev
# Or destroy the droplet entirely (the Reserved IP and volume survive
# unless you remove the `prevent_destroy` lifecycle blocks first)
cd infra/terraform/do
terraform destroy

You now have a working pipeline. Pick what to harden next based on where the project is heading:

  • More than one service: add another entry to deploy-config.yml + another casts/<name>.yaml. The detect/build/cast matrix scales without workflow changes.
  • Production environment: repeat parts 2 & 4 with environment = "prod", a separate droplet, and a per-env GitHub Environment.
  • Persistent storage / databases: Persistent storage + Storage resources.
  • Stricter networking: Network policy.
  • Multi-replica scaling and rollouts: Scale & restart and Health checks.
  • Service-to-service dependencies / init steps: Dependencies and Init steps.

If you want to know how runed works under the covers, start with Concepts → Architecture.


Back: Part 4 — CI/CD · Up: Tutorial overview