cmd / deploy
deploy is a Go CLI that deploys services to Render
from the latest origin/main.
Run it:
go run ./cmd/deploy
It reads these environment variables from .env:
RENDER_API_KEYSENTRY_AUTH_TOKEN
Flow
- Fetch
origin/main, the latest commit merged by sockeye. - Prompt to check Render status for incidents.
- For each service, compare the live commit to
origin/main. If there are new commits, show the log and prompt to deploy. - Deploy the selected services at
origin/main. - Wait for
app-jobsto reachliveso its pre-deploy migration finishes before other services deploy. - Record each service in sockeye as it goes live.
- If anything deployed, create a Sentry release.
Example session:
$ go run ./cmd/deploy
From https://sockeye.example.com/git/app
* 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 these Render services:
app-jobs(migration gate)app-web
Render API client
The render package (render/client.go) wraps these endpoints:
GET /services/:id/deploys?limit=5: find the live deploy's commit SHAPOST /services/:id/deploys: trigger a deploy at a specific commitGET /services/:id/deploys/:deployID: poll deploy status untillive
WaitForDeploy polls every 10s with a 30 minute timeout. Any terminal
state other than live is an error. A failed migration appears as
pre_deploy_failed.
Recording the deploy
The script POSTs a row to sockeye as
each service reaches live, so a failure partway through still records
what shipped. The row stores Render's deploy id.
Render's service names include the environment (app-production).
sockeye names each target with its environment too (app-web-prod), so
the script maps Render's names to sockeye's at the call site.
The script holds no sockeye token. sockeye validates the same personal
token for its API and its git transport. So the script asks git for the
token that soc login stored for the server's host:
in := "protocol=https\nhost=sockeye.example.com\n\n"
c := exec.Command("git", "credential", "fill")
c.Env = append(os.Environ(), "GIT_TERMINAL_PROMPT=0")
One credential lives in ~/.config/sockeye/token, and sockeye names
the token holder as the deployer. That token also pushes and merges,
so CI should hold a service token.
The script looks the token up first, so a missing token stops the deploy before a service ships unrecorded.
Sentry release
After the deploys, the script calls Sentry's API:
- Create a release (version = short SHA, what the UI shows)
- Set release refs (
repository+ full SHA, so the GitHub integration can read commit metadata) - Record a deploy for
production
This connects a Sentry error to the deploy that introduced it.
The three calls are idempotent and run after every deploy reaches
live. A failed release warns and prints the retry command.
Design
The script reads origin/main, so it runs from any branch or
git worktree and ignores
unpushed local commits. origin is sockeye's self-hosted git, and
Render builds from the GitHub mirror, so both read the same commit.
If I skip app-jobs, the script warns that later services may run
against an old schema.
app-web waits too, so a failed web deploy aborts before the Sentry
release. Each service has its own y/n prompt, so I can skip one.