cmd / deploy

deploy is a Go CLI that deploys services to Render from the latest origin/main.

Run from any branch or git worktree:

go run ./cmd/deploy

It requires these environment variables (loaded from .env):

Flow

  1. Fetch origin/main to get the latest merged commit.
  2. Prompt to check Render status for incidents.
  3. For each service, compare the live deploy commit to origin/main. If there are new commits, show the log and prompt to deploy.
  4. Deploy selected services at origin/main.
  5. Wait for app-jobs to reach live so db migrations complete before deploying other services.
  6. If anything deployed, create and tag a Sentry release.

Example session:

$ go run ./cmd/deploy
From https://github.com/org/repo
 * branch            main       -> FETCH_HEAD
Check https://status.render.com/ for incidents before continuing.
Press any key to continue or ctrl+c to exit...

```
a1b2c3d4e fix session expiry on token refresh
f5e6d7c8b add retry logic to webhook delivery
```

deploy app-jobs? (y/n) y
deploying app-jobs...
waiting for app-jobs deploy dep-abc123 to go live...
app-jobs live

skipping app-web, already up to date...

Services

The script deploys multiple Render services:

Render API client

The render package (render/client.go) wraps these endpoints:

WaitForDeploy polls every 10s with a 30 minute timeout. Any terminal state other than live (build_failed, update_failed, pre_deploy_failed, deactivated, canceled) is an error. A failed migration surfaces as pre_deploy_failed.

Sentry release (API, not CLI)

After deploying, the script calls Sentry's API:

  1. Create release (version = short SHA, what the UI shows)
  2. Set release refs (repository + full SHA, so the GitHub integration can pull commit metadata)
  3. Record deploy for production

This connects Sentry errors to the deploy that introduced them.

The three calls are idempotent, and the release runs last, after every deploy is live. A failed release only warns and prints the retry command: the deploys already succeeded.

Design

The script uses origin/main. This makes it work from any git worktree and ignores local main commits that haven't been pushed yet.

app-jobs is the migration gate: waiting for it to reach live confirms its pre-deploy db migration step finished before other services. Skipping it prints a warning that later services may run against an old schema.

Service order matters, and app-web waits too, so a failed web deploy aborts before the Sentry release is tagged.

Each service is deployed independently with a y/n prompt, so you can skip a service if it has unrelated changes or you want to deploy incrementally.

← All articles