Skip to content

blissful-infra deploy

deploy has two targets. By default it ships a service to the project’s local Kubernetes runtime through a real GitOps loop. With --target cloudflare it promotes the same service to Cloudflare.

Terminal window
blissful-infra deploy orders # local kind + ArgoCD + canary
blissful-infra deploy orders --target cloudflare # promote to Cloudflare
Terminal window
blissful-infra deploy [service] [options]

[service] resolves through your use context when omitted.

FlagWhat it does
--tenant <tenant>Tenant (defaults to context)
--project <project>Project (defaults to context, falls back to a registry scan)
--target <target>kubernetes (default, follows the project runtime) or cloudflare
--tag <tag>Image tag (defaults to the service’s git short SHA)
--dry-runShow what would be deployed without making any changes

kubernetes is the default and requires a project created with --runtime kubernetes plus a tenant with a cluster (cluster up). This is the local rehearsal: real GitOps, real canary, on your laptop.

cloudflare is a promotion target rather than a runtime. It works regardless of the project’s local runtime, so runtime: compose | kubernetes keeps meaning “where this runs locally” and stays orthogonal to where it ships. See Promoting to Cloudflare below.

  1. Build the service image from its Dockerfile
  2. kind load the image onto the cluster node: kind-loaded images never pull, so imagePullPolicy is IfNotPresent
  3. Render the Rollout, canary/stable Services and ConfigMap from the gitops templates
  4. Commit and push them to the tenant’s Gitea repo. This is the audit trail
  5. ArgoCD syncs the commit into the project’s namespace
  6. Argo Rollouts starts the canary

Start with --dry-run if you want to see the rendered manifests before anything is committed.

The rollout walks four weights, pausing at each step so you can promote early or abort:

StepWeightThen
110%pause 2m
225%pause 2m
350%pause 5m
4100%done

Pauses are time-based rather than metric-driven, because there is no in-cluster Prometheus yet. Each pause is promotable early from the CLI or the dashboard:

Terminal window
blissful-infra canary status orders
blissful-infra canary promote orders # next step
blissful-infra canary promote orders --full # straight to 100%
blissful-infra canary abort orders # back to stable

More on canary

The tag defaults to the service’s git short SHA, so each deploy is traceable to a commit. Override it when you need to:

Terminal window
blissful-infra deploy orders --tag experiment-1
Terminal window
blissful-infra rollback orders

This reverts the deploy commit in the gitops repo and lets ArgoCD converge back, which survives ArgoCD’s selfHeal, unlike an imperative rollback. More on rollback

The dashboard’s Environments tab shows ArgoCD sync state and a live canary card with a weight bar, step counter and Promote / Promote Full / Abort buttons:

Terminal window
blissful-infra dashboard up

You can also open ArgoCD and Gitea directly. cluster up prints both URLs and their credentials.

Terminal window
blissful-infra deploy orders --target cloudflare

Local kind is the rehearsal. Cloudflare is production. The same service source ships to both.

Cloudflare Workers run Web-standard fetch handlers on a V8 isolate. There is no JVM and no CPython, so spring-boot and lambda-python backends cannot be promoted. This is a structural limit, not a missing feature.

Service typeTemplateCloudflare product
backend / workerhonoWorkers
frontendreact-vitePages
backendspring-boot, lambda-pythonnot eligible

Trying to promote an ineligible service fails immediately with a pointer to --template hono, rather than an opaque wrangler error at upload time.

Terminal window
blissful-infra service add orders --type backend --template hono

The hono template splits its entry points so a single service has two homes:

FileRole
src/app.tsThe application. Imports nothing from node:*, which is what makes it portable
src/server.tsNode entry via @hono/node-server on port 8080. This is what the container image runs
src/worker.tsDefault-exports the app, which is already a Workers fetch handler

So the service runs under the compose and kubernetes runtimes exactly like any other, and the same code promotes to Workers with no rewrite.

Terminal window
npm install -g wrangler@latest
wrangler login

Both are checked up front rather than mid-deploy.

Everything defaults from the service coordinates, so an absent config still deploys. Worker and Pages names both default to <project>-<service>. To override, add a deploy.cloudflare block to the service’s service.yaml:

deploy:
cloudflare:
workerName: shop-orders
pagesProject: shop-web
accountId: abc123
d1Database: shop-orders-db
kvNamespace: shop-orders-cache

D1 and KV are provisioned only when declared. “Already exists” counts as success, so redeploys are idempotent. If the service has a d1:migrate script, migrations run with --remote after provisioning.

accountId is passed as CLOUDFLARE_ACCOUNT_ID because wrangler has no per-command flag for it.

This is the sharpest edge in the design and worth understanding before you rely on it.

A service on Cloudflare has no Kafka and cannot reach the project’s shared Postgres. D1 is the substitute for persistence, which means the data layer genuinely differs between your local rehearsal and production. Nothing migrates schemas between the two.

Pages and Workers deploys are also not progressive. There is no canary equivalent to the local rollout; rollback is wrangler rollback.