Deploying to App Platform
What the deployment plan contains, how a release is verified, and what nextship refuses to touch.
nextship deploy # print the plan
nextship deploy --yes # execute itOptions#
| Option | Default | Meaning |
|---|---|---|
--yes | off | Execute the plan. Without it, deploy only prints |
--region <slug> | blr | App Platform region. Recorded on the first deploy and reused after that |
--size <slug> | apps-s-1vcpu-0.5gb | Instance size |
--registry <name> | the app name | Registry name, which must be unique across all of DigitalOcean |
The plan comes first, every time#
The image tag is derived from the build#
The tag is the deployment id, computed from the content of the build: the nextship version, the generated Dockerfile, the adapter and prune script, the Server Actions key, and the name and contents of every env file.
Change any of them and the tag changes. That is what stops an edited env file from shipping under a tag that already exists.
When the working tree is not a clean commit, the id is unique to that build instead, and the CLI says so rather than implying a reproducibility it cannot provide:
deployment dpl-2b2c3e535f1e-ebee628b
this id is unique to this build, because the source is not a clean commitYour settings survive#
App Platform replaces the whole app spec on update. A naive deploy would silently drop anything nextship does not know about.
So nextship merges instead of overwriting. Ingress rules, components you added by hand,
and environment variables set in the control panel are all carried forward. This was found
by checking a real spec before writing to it: an ingress block would have been lost.
It waits for the app to actually serve#
A release is not finished when the API accepts it. nextship polls until the deployment reports that it is live, then prints the URL. It stops waiting after 15 minutes and says so; the deployment is not cancelled and may still finish, so the control panel has the final word.
The URL it prints is the platform host, not the field the API calls live_url. That field
becomes your primary custom domain the moment you attach one, which means it points at a
name that does not resolve until you have created the DNS record.
Pages the CDN keeps#
App Platform serves your app through Cloudflare, and Cloudflare honours the caching Next.js
asks for. For a page with no revalidate time, Next.js sends Cache-Control: s-maxage=31536000, a year. So when a Server Action calls revalidatePath or
revalidateTag, the container renders the new page, but visitors can keep getting the copy
the edge already holds. It works in local testing, because nothing sits in front of the
container there.
Give every page you revalidate on demand a revalidate time:
export const revalidate = 30The page then sends s-maxage=30, and the edge keeps it at most that long. Our own
end-to-end test on macOS measured the change reaching visitors in about 30 seconds this
way. nextship doctor warns when your source revalidates on demand.
What it will not do#
It only ever touches the app recorded in nextship.json. It creates nothing else, and it
deletes nothing at all. If the recorded app is missing, it says so and stops rather than
creating a replacement that would leave the original orphaned and billing.
One change at a time#
deploy, env push, env rm, domain add and domain rm refuse while the app has a
deployment still in progress, because a second change would replace the first part way
through. deploy checks before it builds, so it says so immediately rather than after a
full build and push.
rollback is the exception. It is how you get away from a bad deployment, including one
still rolling out, so whether it is possible is left to App Platform's own rollback
validation.

