Managing deployments
Serve a build on an app's targets, promote and roll back a deployment, add a second target, and run the commands from a script. The commands need a repository that is published as an app: district app create publishes one and prints the app's primary target.
Each command reads the clone in the current directory. The clone's origin identifies the repository and the environment. A positional repository argument names one from outside a clone. The commands use production. district login --staging records staging as the current environment. Every later command that takes no environment flag uses it.
Deploy to the primary target
district deploy builds the repository and serves the build on the app's primary target. The ref is the clone's HEAD object name. A commit that origin does not hold is refused before the build starts, so push the branch first.
The command prints the repository, the ref, the target, and the environment, then asks Deploy? [y/N]. --yes skips the question. The build steps and the deployment status go to stderr, and the URL goes to stdout, so a pipe reads the URL alone.
$ district deploy --yes
build hbld_01K4… started (re-attach with: district build attach hbld_01K4…)
✔ checkout succeeded 3 s
✔ install succeeded 41 s
✔ build succeeded 96 s
✔ bundle succeeded 7 s
✔ export succeeded 2 s
✔ deployment hdep_01K4… succeeded
pinned https://hdep-01k4….ondistrict.net/ · serves this deployment only, not the site address
env production
https://shopfront.ondistrict.net/A deployment can succeed without becoming the target's current deployment: a newer deployment finished first, or an operator pointed the target at another deployment. The command then exits non-zero and names both the deployment the target serves and the command that repoints it:
deployment hdep_01K4… succeeded but shopfront.ondistrict.net serves hdep_01K4…E. Point the hostname at it with: district target set-deployment shopfront hdep_01K4…district target list reports what each target serves.
Stage a preview, then promote it
district deploy --new-preview creates a preview target with the build. Its label is generated, and it expires 7 days after its last successful deployment. --target with --new-preview is a usage error, because each build has one deploy intent.
# 1. Build the current branch and serve it on a new preview target
district deploy --new-preview
# → https://pr-42.ondistrict.net/
# 2. Check the preview and read the id of the deployment it serves
district target list
# 3. Deploy the preview's build to the primary target, with no new build
district deploy --from hdep_01K4…
# → https://shopfront.ondistrict.net/A preview target uses the preview environment and the primary target uses production. Each environment binds its own addons, so a target serves only deployments made for its environment. district target set-deployment refuses the preview's deployment on the primary target:
$ district target set-deployment shopfront hdep_01K4…
deployment hdep_01K4… was made in another env than shopfront, which uses env production. Deploy its build to shopfront with: district deploy --from hdep_01K4… --target shopfrontThe promotion is a new deployment for the primary target. district deploy --from <hdep_…> deploys the build of the preview's deployment to the primary target, in the production environment, and runs no new build. A build keeps its files for 7 days. After that, the API refuses the deployment, and its message says to build the repository again. Bindings and environments describes the environments.
The preview target needs no cleanup. Each successful deployment on it moves its expiry 7 days ahead, and a set-deployment call on it does the same.
Roll back to an earlier deployment
A rollback points a target at an earlier deployment of its environment with district target set-deployment. It runs no build, and it does not change the data in an addon. A deployment made before environments existed has no environment, so a target of any environment can roll back to it. district target list names the deployment each target serves, and district build list names the ref each build was built from. district deploy --json prints the id of the deployment it made as .deployment.id, so a script can record each one.
$ district target list
LABEL HOSTNAME KIND ENV EXPIRES DOMAINS DEPLOYMENT DEPLOYMENT ENV STATUS
shopfront shopfront.ondistrict.net primary production - shop.example.com (active) hdep_01K4… production succeeded
pr-42 pr-42.ondistrict.net preview preview 2026-09-04T09:00:00Z hdep_01K4… preview succeededA value that is not a hdep_ id is a usage error before any call. The API refuses a deployment that did not succeed, one whose script was removed, one that belongs to another app, and an expired preview target. It uses its own message for each refusal. For a deployment of another environment, the command names the district deploy --from command that deploys its build to the target.
Add a second target and make it primary
An app can have at most 5 durable targets and 20 unexpired preview targets. Exactly one live target is primary, and it is durable. district target create takes a label for a durable target and --preview for a preview one. A command line with neither, and one with both, are usage errors.
# 1. Add a second durable target
district target create next
# 2. Build the current branch and serve it on that target
district deploy --target next
# 3. Make it the app's primary target
district target set-primary next
# 4. Delete the old target when nothing must serve it
district target delete shopfrontdistrict target set-primary prints primary next (next.ondistrict.net). The previous primary target stays live and serves what it served. A preview target is refused with the API's own message.
district target delete deletes a target, and the host answers 404 within about 15 seconds. The primary target cannot be deleted: promote another durable target first. The API refuses a target that custom domains serve, and its message names those domains. Remove each domain first with district domain remove. The label is unavailable to other workspaces for one hour after the deletion.
Follow a build from another machine
The build runs remotely, not in the terminal. Ctrl-C does not stop the build. district build and district deploy print the build id before they wait:
build hbld_01K4… started (re-attach with: district build attach hbld_01K4…)From another machine, district build list finds the id:
$ district build list my-app --first 5
BUILD STATUS REF CREATED
hbld_01K4… succeeded main 2026-08-25T09:00:00Z
hbld_01K4… failed topic 2026-08-24T09:00:00Zdistrict build attach hbld_01K4… then reports that build as district build does: the same step lines, the same --json object, the same failing-step log, and the same exit codes. It starts nothing, so it asks for no confirmation. A build id names no environment, so the environment is the one a flag names, the one the last district login recorded, or production. An id the caller cannot read is a usage error: no build hbld_01K4… in staging.
A deployment continues after the CLI exits. Once it exists, district target list reports what the target serves.
Script a build or a deploy
--yes skips the confirmation, and --json replaces the progress lines with one JSON value on stdout. Without a terminal and without --yes, the command stops, because an agent cannot answer the question.
district build --yes --json > build.json
jq -r .status build.jsondistrict build --json prints the final build. It does not query logs. A script that needs the log queries HostingBuildStep.logTail.
district deploy --json prints { "build": …, "deployment": … } as one object. Each outcome with a build prints this object, so a script can read .build.status for every build outcome. "deployment": null identifies an outcome with no deployment to print: a build that stopped before one existed, or a deployment that disappeared while the command read it. An outcome with no build, such as a declined confirmation, prints nothing.
In a script, --json carries the preview's deployment id to the primary deploy, and --from deploys its build with no new build. The && chain stops at a failed deploy, and jq -er rejects an outcome with no deployment:
district deploy --new-preview --yes --json > preview.json &&
deployment="$(jq -er .deployment.id preview.json)" &&
district deploy --from "$deployment" --yes --json > primary.jsonExit code 0 means the target serves the deployment. Exit code 1 means the run failed: the build failed or errored, the confirmation was declined, no deployment appeared, the deployment failed, or the target serves another deployment. Exit code 2 means the command line names nothing runnable: no repository or target, a HEAD that is not pushed, or no terminal to confirm. The reference entry lists each code.
Store a session on the machine that runs the job. An account that belongs to several organizations also runs district org select once, because the organization prompt needs a terminal.