nextshipnextship
Deploying to App Platform

Deploying to App Platform

What the deployment plan contains, how a release is verified, and what nextship refuses to touch.

Terminal
nextship deploy        # print the plan
nextship deploy --yes  # execute it

Options#

OptionDefaultMeaning
--yesoffExecute the plan. Without it, deploy only prints
--region <slug>blrApp Platform region. Recorded on the first deploy and reused after that
--size <slug>apps-s-1vcpu-0.5gbInstance size
--registry <name>the app nameRegistry 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:

Output
  deployment dpl-2b2c3e535f1e-ebee628b
  this id is unique to this build, because the source is not a clean commit

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

app/posts/page.tsx
export const revalidate = 30

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