Deployments · Pre-deploy commands
Pre-deploy commands
Run your database migrations against the new version before it serves a single request. The command runs once per build, in the new image, with your service's variables and secrets. If it fails, nothing rolls out.
Where it runs in a deploy
| Step | What happens | If it fails |
|---|---|---|
| 1. Build | Your source is built into a new image. | The build fails. Nothing changes. |
| 2. Pre-deploy command | The command runs once, in the new image, inside the service's environment. | The build fails with the command's exit code. Nothing rolls out, and the current version keeps serving. |
| 3. Rollout | New spherelets start, pass their health check, and take traffic from the old ones. | The rollout stops. The previous version keeps serving. |
What the command gets
| Image | The image the build just produced. Your code, dependencies, and migration files are all there. |
| Variables and secrets | The service's variables and secrets scoped Build & runtime or Runtime only — the same values the running service gets, including the ones inherited from the environment. Build only values are not set. See scopes. |
| Network | The service's environment. Your datastores and other services on the private network are reachable exactly as they are from the running service. |
| Resources | The vCPU and memory of one spherelet of the service's shape (Flex, Standard, or Performance). |
| Time limit | 30 minutes. |
| Runs | Once per build — not once per spherelet — with no automatic retry. |
| Output | On the build in the Builds tab, after the build output, under Pre-deploy command output. |
Set it
A pre-deploy command is part of the service's build manifest: the pre_deploy_command field. Each environment's deployment of a service keeps its own manifest, so production and staging can run different commands.
Read the current manifest first, add the field, and write it back. PUT stores the whole manifest, so send every field you want to keep.
# 1. Read the manifest the next build will use
curl --request GET \
--url "https://api.computesphere.com/v2/deployments/DEPLOYMENT_ID/manifest" \
--header "Authorization: Bearer YOUR_API_TOKEN"
# 2. Store it back with pre_deploy_command added
curl --request PUT \
--url "https://api.computesphere.com/v2/deployments/DEPLOYMENT_ID/manifest" \
--header "Authorization: Bearer YOUR_API_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"service": "api",
"runtime": { "type": "docker", "dockerfile": "Dockerfile", "port": 8000 },
"pre_deploy_command": "npx prisma migrate deploy"
}'
Saving the manifest doesn't start a build. The command runs from the next build on — a push, a manual Deploy, or a Save & rebuild. Remove it by storing the manifest without the field.
Shell form or exec form
| You write | It runs as | Use when |
|---|---|---|
npx prisma migrate deploy | /bin/sh -c "npx prisma migrate deploy" — quotes, $VARS, pipes, and && work | Your image has a shell (most images) |
["/app/migrate", "up"] | The program directly, with those arguments. No shell. | Your image has no shell — distroless or FROM scratch images. Use the full path to the program. |
A shell-form command in an image without /bin/sh fails before it starts. The build shows "The pre-deploy command couldn't start because its program isn't in the built image. For an image without a shell, write the command as a JSON array with the full path, like ["/app/migrate", "up"]."
Common commands
| Stack | pre_deploy_command |
|---|---|
| Prisma | npx prisma migrate deploy |
| Django | python manage.py migrate --noinput |
| Rails | bundle exec rails db:migrate |
| Laravel | php artisan migrate --force |
| Go binary in a distroless image | ["/app/migrate", "up"] |
| Several steps | ./bin/migrate && ./bin/warm-cache |
Which actions run it
| Action | Runs the pre-deploy command? |
|---|---|
| Build — a push, a manual Deploy, Save & rebuild | Yes |
| Promote | Yes — the target environment's command, with the target's variables |
| Redeploy, Save & redeploy | No — no new image |
| Restart, Start | No |
| Rollback | No — the restored image already went through its release |
When it fails
A failed pre-deploy command fails the build. Nothing rolls out, the current version keeps serving, and the build shows the reason:
| Build shows | Meaning | What to do |
|---|---|---|
| Pre-deploy command exited with code N. | Your command returned a non-zero exit code. | Read Pre-deploy command output on the build, fix the cause, and build again. |
| Pre-deploy command ran out of memory (exit code N). | The command needed more memory than one spherelet of the service's shape. | Reduce the command's memory use, or move the service to a larger shape. |
| Pre-deploy command exceeded the 30-minute time limit and was terminated by the platform. | The command ran for 30 minutes. | Split long data backfills into a background worker or a cron job. |
| The pre-deploy command couldn't start because its program isn't in the built image… | The program — often /bin/sh — isn't in the image. | Use the exec form with a full path, or add the program to the image. |
| The pre-deploy command was stopped before it finished. | The build was canceled, or replaced by a newer one, while the command ran. | Build again if you still want the release. |
Cancel build on the Builds tab stops the command right away, the same as it stops a build.
Honest limits
- Make the command idempotent. Every build runs it again, and a failed or canceled build leaves any work it already did in place. Migration tools that track applied migrations (Prisma, Django, Rails, Laravel, golang-migrate) are safe to rerun; a hand-written
ALTER TABLEwithout a guard isn't. - It isn't a rollback plan. Rolling back restores the previous image, not the previous schema, and doesn't run any command. Keep migrations backward compatible with the version that's still serving — add columns before code uses them, remove them after it stops.
- No interactive input. The command runs without a terminal. Anything that prompts will hang until the 30-minute limit.
- The API is the only way to set it today. The console and
computesphere.yaml/ thecsphCLI don't exposepre_deploy_commandyet. - Services only. Static sites publish files and have no runnable image, so there's nothing to run a command in.
- One spherelet of resources. A migration that needs more memory than the service's shape offers needs a larger shape or a smaller batch size.
What's next
- Environment variables — which values the command receives, and how scopes work
- Promote — ship a tested image to another environment; the target's pre-deploy command runs first
- Service actions — Deploy, Redeploy, Restart, Rollback, and what each one runs
- Reliability — zero-downtime rollouts and what rollback does and doesn't undo
- PostgreSQL — the managed database most pre-deploy migrations target