The most common scripted Render operation is “ship this.” render deploys create covers every variant - autodeploy nudge, specific commit, specific image, with or without waiting. This step walks through them all and ends with the deploy helper you’ll plug into CI in step 10.
The shape of a deploy command
render deploys create <SERVICE_ID> [flags]| Flag | What it does | When to use |
|---|---|---|
--wait | Block until the deploy succeeds or fails; exit code reflects outcome | Any CI job |
--confirm | Skip the “are you sure?” prompt | Any non-interactive use |
-o json | Machine-readable output | Any scripted use |
--commit <SHA> | Deploy that exact commit | Rollbacks, specific-SHA deploys |
--image <URL> | Deploy that exact Docker image | Image-runtime services |
--clear-cache | Clear build cache before deploying | Debugging a stuck build |
The combination most CI jobs want: --wait --confirm -o json. The combination most rollback scripts want: --commit <SHA> --wait --confirm.
Trigger a fresh deploy (autodeploy-style)
The simplest case - kick the service into building whatever the autodeploy branch points to:
SRV=$(render-id api)render deploys create "$SRV" --wait --confirm -o jsonThe command prints the deploy object as it progresses, and exits 0 on live or non-zero on build_failed / update_failed. That’s the single most important property - your script can branch on success or failure without parsing any logs.
Deploy a specific commit (rollbacks)
This is the one you’ll reach for at 2 a.m. when something just shipped and you need to revert:
PREV_GOOD_SHA="a1b2c3d4"render deploys create "$SRV" \ --commit "$PREV_GOOD_SHA" \ --wait --confirm -o jsonRender does a fresh build at that SHA and ships it. The autodeploy branch is unchanged, so the next push to main still wins - this is a true point-in-time rollback, not a permanent reassignment.
Deploy a specific image (image runtimes)
For services whose render.yaml uses runtime: image, you skip the build entirely:
render deploys create "$SRV" \ --image ghcr.io/acme/app:v1.4.2 \ --wait --confirm -o jsonThe image must be accessible to Render (public, or with registry credentials configured on the service). Useful when builds happen outside Render - e.g. a separate CI job builds and pushes the image, then a follow-up job tells Render to deploy it.
Inspect deploy history
render deploys list is the audit trail. Useful filters with jq:
# All deploys for a servicerender deploys list "$SRV" -o json | jq '.[] |.deploy.status'
# Last 5 deploys with SHA + status + creation timerender deploys list "$SRV" -o json \ | jq -r '.[0:5][] | "\(.deploy.status)\t\(.deploy.commit.id[0:7])\t\(.deploy.createdAt)"'
# Most recent failed deployrender deploys list "$SRV" -o json \ | jq '.[] | select(.deploy.status == "build_failed" or.deploy.status == "update_failed") |.deploy' \ | head -1The known status strings: created, build_in_progress, update_in_progress, live, deactivated, build_failed, update_failed, canceled, pre_deploy_in_progress, pre_deploy_failed.
Exit codes: the contract
--wait makes render deploys create behave like any other CLI tool. The contract:
| Exit code | Meaning |
|---|---|
0 | Deploy reached live |
1 | Deploy reached build_failed, update_failed, or canceled |
2+ | CLI usage error (bad flag, missing service, auth failure) |
Your CI scripts treat non-zero as failure, which gives you the right behavior for free:
#!/usr/bin/env bashset -euo pipefailSRV="${RENDER_SERVICE_ID:?must be set}"
if ! render deploys create "$SRV" --wait --confirm -o json > deploy.json; then echo "::error::Deploy failed" >&2 jq '.deploy.status,.deploy.errorMessage // empty' deploy.json >&2 exit 1fi
echo "Deploy succeeded: $(jq -r '.deploy.commit.id' deploy.json)"The > deploy.json keeps the structured output for downstream steps (you’ll use this in step 10 to fetch logs on failure).
A reusable deploy helper
The pattern you’ll use everywhere - and the heart of the capstone:
#!/usr/bin/env bashset -euo pipefailexport RENDER_API_KEY="${RENDER_API_KEY:?must be set}"export RENDER_OUTPUT=json
usage() { echo "Usage: $0 <service-id-or-name> [--commit SHA] [--image URL]" >&2 exit 2}
[[ $# -lt 1 ]] && usage
target="$1"; shift
if [[ "$target" =~ ^srv- || "$target" =~ ^dpg- || "$target" =~ ^red- ]]; then srv="$target"else srv=$(render services \ | jq -r --arg n "$target" '.[] | select(.service.name == $n) |.service.id') [[ -z "$srv" ]] && { echo "No service named '$target'" >&2; exit 2; }fi
echo "Deploying $srv..."render deploys create "$srv" --wait --confirm "$@"chmod +x render-deploy.sh
./render-deploy.sh api./render-deploy.sh api --commit a1b2c3d4./render-deploy.sh api --image ghcr.io/acme/api:v1.4.2
./render-deploy.sh srv-xxxxxxxxxxxxxxxxThe helper accepts a service ID or a name (resolved via the recipe from step 05), forwards any extra flags through to render deploys create, and follows the scripting template from step 04.
Diagnose a failed deploy
When a deploy fails, three things give you the picture:
flowchart LR fail["Deploy failed"] list["render deploys list (status + errorMessage)"] logs["render logs -r SRV (build/runtime output)"] ssh["render ssh -e SRV (poke around in an ephemeral shell)"] fail --> list list --> logs logs --> ssh
Steps 07 (logs) and 08 (SSH) go deep on the last two. For now, the quickest “what happened?” command:
render deploys list "$SRV" -o json \ | jq '.[0].deploy | {status, errorMessage, finishedAt, commit:.commit.id}'The combination of status and errorMessage usually tells you whether the failure is build-time (build_failed - look at the build logs) or runtime (update_failed - look at the service logs).
What you give up with --wait
--wait polls the deploy every few seconds and blocks until it finishes. For a clean build of a small service that’s ~3-5 minutes; for a slow Docker build it can be 15-20. In CI that’s fine - you want the job to block. Locally it’s sometimes too long.
For local “fire and forget” workflows:
render deploys create "$SRV" --confirm -o json | jq -r '.deploy.id'Without --wait, you get the deploy ID immediately. Stash it, do something else, come back later:
DEPLOY_ID=$(render deploys create "$SRV" --confirm -o json | jq -r '.deploy.id')
render deploys list "$SRV" -o json \ | jq --arg id "$DEPLOY_ID" '.[] | select(.deploy.id == $id) |.deploy.status'What you learned
- `render deploys create SRV --wait --confirm -o json` is the canonical CI command - exit code matches deploy outcome
- `--commit SHA` is your rollback button; `--image URL` is for `runtime: image` services
- `render deploys list` plus `jq` gives you the audit trail and the failure diagnosis (status + errorMessage)
- Without `--wait`, success only means 'request accepted' - use it locally for fire-and-forget, never in CI
- The `render-deploy.sh` helper in this step is the same shape you'll plug into GitHub Actions in step 10