Render Tutorials
Render CLI for power users

Trigger deploys you can trust

⏱ 10 min

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

Terminal
render deploys create <SERVICE_ID> [flags]
FlagWhat it doesWhen to use
--waitBlock until the deploy succeeds or fails; exit code reflects outcomeAny CI job
--confirmSkip the “are you sure?” promptAny non-interactive use
-o jsonMachine-readable outputAny scripted use
--commit <SHA>Deploy that exact commitRollbacks, specific-SHA deploys
--image <URL>Deploy that exact Docker imageImage-runtime services
--clear-cacheClear build cache before deployingDebugging 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:

Terminal
SRV=$(render-id api)
render deploys create "$SRV" --wait --confirm -o json

The 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:

Terminal
PREV_GOOD_SHA="a1b2c3d4"
render deploys create "$SRV" \
--commit "$PREV_GOOD_SHA" \
--wait --confirm -o json

Render 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:

Terminal
render deploys create "$SRV" \
--image ghcr.io/acme/app:v1.4.2 \
--wait --confirm -o json

The 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:

Terminal
# All deploys for a service
render deploys list "$SRV" -o json | jq '.[] |.deploy.status'
# Last 5 deploys with SHA + status + creation time
render deploys list "$SRV" -o json \
| jq -r '.[0:5][] | "\(.deploy.status)\t\(.deploy.commit.id[0:7])\t\(.deploy.createdAt)"'
# Most recent failed deploy
render deploys list "$SRV" -o json \
| jq '.[] | select(.deploy.status == "build_failed" or.deploy.status == "update_failed") |.deploy' \
| head -1

The 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 codeMeaning
0Deploy reached live
1Deploy 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:

ci-deploy.sh
#!/usr/bin/env bash
set -euo pipefail
SRV="${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 1
fi
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:

render-deploy.sh
#!/usr/bin/env bash
set -euo pipefail
export 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 "$@"
Terminal
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-xxxxxxxxxxxxxxxx

The 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:

Terminal
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:

Terminal
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:

Terminal
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'
Your CI job uses `render deploys create srv-xxx --confirm -o json` (no `--wait`) and reports success even when the deploy actually fails on Render. What's happening?

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