ship-it — go from "works on my machine" to "live on the internet"
When to use this skill
Trigger when the user wants deployment work done. Strong signals:
- "deploy this", "ship it", "put this live"
- "set up Vercel / Railway / Fly / Render"
- "dockerize this", "write a Dockerfile"
- "set up CI/CD for this repo"
- "write the deploy GitHub Action"
Do not trigger for: infrastructure architecture from scratch (use a real infra discussion first), DB provisioning (use schema-architect for schema, then a platform skill for hosting), or for production debugging after a deploy went wrong (read logs, don't redeploy blindly).
The output contract
A deployable artifact that:
- Has a single, clear deploy command —
git push, vercel, flyctl deploy, railway up, whatever the platform's idiom is.
- Boots in production-like config — secrets via env, no hard-coded localhost, no dev-only middleware in the prod bundle.
- Has health checks — the platform knows when the app is up.
- Has logs the team can read — structured JSON, going somewhere they can find them.
- Documents rollback — one command or one button.
Workflow
1 — Match the app to the platform
Ask 4 questions before recommending:
- What is it? Static site, Next.js, API + DB, full-stack monolith, background workers, ML inference?
- Where's the data? Already on managed DB (Supabase, Neon, PlanetScale)? Need one? Local SQLite that needs migrating to managed?
- What's the budget profile? Generous (AWS, GCP), tight (Railway, Render, Fly, Vercel hobby), zero (Cloudflare/Vercel free tiers)?
- What's the team's ops capacity? Real DevOps engineer on staff, or solo founder?
Then match:
- Static site / Next.js / Remix / SvelteKit → Vercel (default), Cloudflare Pages, Netlify
- Full-stack monolith with a DB → Railway, Render, Fly.io
- API + workers + cron → Fly.io, Railway, Render
- Containerized anything, multi-region → Fly.io
- Already on AWS / GCP → stay there; use ECS/Cloud Run/Lambda + appropriate manager
- Edge functions, global low-latency → Cloudflare Workers, Vercel Edge
State the choice + 2 reasons in plain prose. Don't bury it in YAML.
2 — Write the platform config
Vercel (vercel.json): only if the defaults don't fit. Most Next.js projects need nothing.
Railway (railway.toml):
[build]
builder = "NIXPACKS"
[deploy]
startCommand = "node dist/server.js"
healthcheckPath = "/health"
healthcheckTimeout = 30
restartPolicyType = "ON_FAILURE"
restartPolicyMaxRetries = 3
Fly.io (fly.toml):
app = "myapp"
primary_region = "ord"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = "stop"
auto_start_machines = true
min_machines_running = 1
[[http_service.checks]]
grace_period = "10s"
interval = "30s"
timeout = "5s"
method = "GET"
path = "/health"
Render (render.yaml): blueprint-driven; commit the file and the service is reproducible.
3 — Dockerfile when needed
Use a multi-stage build. Production image must be slim and deterministic.
# syntax=docker/dockerfile:1.7
FROM node:20-bookworm-slim AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
FROM node:20-bookworm-slim AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build
FROM node:20-bookworm-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nodejs
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY package.json ./
USER nodejs
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s CMD node -e "fetch('http://localhost:8080/health').then(r=>r.ok?process.exit(0):process.exit(1))"
CMD ["node", "dist/server.js"]
Rules: pin the base image to a major + distro tag (node:20-bookworm-slim), run as non-root, copy only what's needed into the runtime stage, expose one port.
Add a .dockerignore that mirrors .gitignore plus node_modules, .git, *.md, tests/, coverage/.
4 — GitHub Actions CI/CD
Minimum viable for a typical Node app deploying to Fly:
name: deploy
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm test
deploy:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: superfly/flyctl-actions/setup-flyctl@master
- run: flyctl deploy --remote-only
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
Rules: never deploy without tests passing. Use concurrency to prevent two deploys racing. Pin action versions to a specific tag (not @main).
5 — Secrets & env
- Never commit
.env. .gitignore it.
- Maintain
.env.example listing every required variable with a comment about where to get the value.
- For each platform, document the secrets dashboard URL.
- Rotate before launch: any secret that was used in dev or shared in a code review gets rotated before first prod traffic.
6 — First deploy
Walk through together:
- Provision the project on the platform (CLI:
fly launch, railway init, vercel link).
- Set the secrets via the platform's CLI or dashboard.
- Deploy:
fly deploy / railway up / vercel --prod.
- Hit the health endpoint and the headline URL. Confirm 200.
- Tail logs for 60 seconds. Confirm no crash-loop, no spammy error.
7 — Document rollback
Add a DEPLOY.md (or section in README) with:
- Platform + region(s)
- The deploy command
- How to read logs (
fly logs, railway logs --follow, etc.)
- The rollback command (
fly releases + fly deploy --image <previous>, Vercel "Promote to Production" on prior deployment, etc.)
- Who has access to the dashboard
Patterns and anti-patterns
✅ Do:
- Pin every base image, every action, every CLI version. "Latest" is a time bomb.
- Health check on a path that exercises the app (
/health that pings the DB), not just /.
- Use the platform's native secrets store; don't reinvent with KMS unless you actually need it.
- Build once, deploy the same artifact to staging then prod (immutable images).
❌ Don't:
- Don't
apt install random tools in a runtime image. The build stage is for that.
- Don't bake secrets into images. Ever.
- Don't deploy on every push to a feature branch. Trigger on
main (or after a PR merge).
- Don't set up Kubernetes for a 1-service app. The cost is operational, not just dollar.
- Don't skip the health check thinking "it'll be fine". Without one, the platform routes traffic to a crashing instance.
Example invocation
User: "I have a Next.js app with a Postgres DB. Ship it. Cheap as possible."
- Recommend: Vercel for the Next.js front, Neon for Postgres (generous free tier, branchable for previews). Reasons: zero-config Next.js deploys; Neon's pooled connections work with Vercel serverless.
- Wire
DATABASE_URL as a Vercel env var (set for Production + Preview, not Development).
- Add
vercel.json only if needed (regions, edge runtime). For most Next.js, defaults are correct.
- Add
.env.example with DATABASE_URL=postgres://....
- GitHub Actions: run lint + typecheck + tests on PRs; Vercel handles the deploy automatically on merge.
- First deploy:
vercel --prod. Verify health endpoint, log tail for 60s.
- Document rollback: Vercel dashboard → previous deployment → Promote to Production.
See also
schema-architect — for the DB migration that must run before/with the first deploy
security-sentinel — sweep for committed secrets before going live
git-flow-pro — set up the protected main branch and required checks before connecting CI
1---2name: ship-it3description: Set up or fix a deploy pipeline. Picks a platform that fits the app, writes the config (Dockerfile, vercel.json, railway.toml, fly.toml, GitHub Actions), and ships a first deploy. Knows Vercel, Railway, Fly.io, Render, AWS basics (ECS, Lambda, Amplify), Docker, Kubernetes essentials, and GitHub Actions. Use when the user says "deploy this", "ship it", "set up vercel", "dockerize this", "write the GitHub Actions for deploy", or has working local code that needs to be live.4---56# ship-it — go from "works on my machine" to "live on the internet"78## When to use this skill910Trigger when the user wants deployment work done. Strong signals:1112- "deploy this", "ship it", "put this live"13- "set up Vercel / Railway / Fly / Render"14- "dockerize this", "write a Dockerfile"15- "set up CI/CD for this repo"16- "write the deploy GitHub Action"1718Do *not* trigger for: infrastructure architecture from scratch (use a real infra discussion first), DB provisioning (use `schema-architect` for schema, then a platform skill for hosting), or for production debugging after a deploy went wrong (read logs, don't redeploy blindly).1920## The output contract2122A deployable artifact that:23241. **Has a single, clear deploy command** — `git push`, `vercel`, `flyctl deploy`, `railway up`, whatever the platform's idiom is.252. **Boots in production-like config** — secrets via env, no hard-coded localhost, no dev-only middleware in the prod bundle.263. **Has health checks** — the platform knows when the app is up.274. **Has logs the team can read** — structured JSON, going somewhere they can find them.285. **Documents rollback** — one command or one button.2930## Workflow3132### 1 — Match the app to the platform3334Ask 4 questions before recommending:35361. **What is it?** Static site, Next.js, API + DB, full-stack monolith, background workers, ML inference?372. **Where's the data?** Already on managed DB (Supabase, Neon, PlanetScale)? Need one? Local SQLite that needs migrating to managed?383. **What's the budget profile?** Generous (AWS, GCP), tight (Railway, Render, Fly, Vercel hobby), zero (Cloudflare/Vercel free tiers)?394. **What's the team's ops capacity?** Real DevOps engineer on staff, or solo founder?4041Then match:4243- **Static site / Next.js / Remix / SvelteKit** → Vercel (default), Cloudflare Pages, Netlify44- **Full-stack monolith with a DB** → Railway, Render, Fly.io45- **API + workers + cron** → Fly.io, Railway, Render46- **Containerized anything, multi-region** → Fly.io47- **Already on AWS / GCP** → stay there; use ECS/Cloud Run/Lambda + appropriate manager48- **Edge functions, global low-latency** → Cloudflare Workers, Vercel Edge4950State the choice + 2 reasons in plain prose. Don't bury it in YAML.5152### 2 — Write the platform config5354**Vercel** (`vercel.json`): only if the defaults don't fit. Most Next.js projects need nothing.5556**Railway** (`railway.toml`):57```toml58[build]59builder = "NIXPACKS"6061[deploy]62startCommand = "node dist/server.js"63healthcheckPath = "/health"64healthcheckTimeout = 3065restartPolicyType = "ON_FAILURE"66restartPolicyMaxRetries = 367```6869**Fly.io** (`fly.toml`):70```toml71app = "myapp"72primary_region = "ord"7374[build]75 dockerfile = "Dockerfile"7677[http_service]78 internal_port = 808079 force_https = true80 auto_stop_machines = "stop"81 auto_start_machines = true82 min_machines_running = 18384[[http_service.checks]]85 grace_period = "10s"86 interval = "30s"87 timeout = "5s"88 method = "GET"89 path = "/health"90```9192**Render** (`render.yaml`): blueprint-driven; commit the file and the service is reproducible.9394### 3 — Dockerfile when needed9596Use a multi-stage build. Production image must be slim and deterministic.9798```dockerfile99# syntax=docker/dockerfile:1.7100FROM node:20-bookworm-slim AS deps101WORKDIR /app102COPY package.json package-lock.json ./103RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev104105FROM node:20-bookworm-slim AS build106WORKDIR /app107COPY package.json package-lock.json ./108RUN --mount=type=cache,target=/root/.npm npm ci109COPY . .110RUN npm run build111112FROM node:20-bookworm-slim AS runtime113WORKDIR /app114ENV NODE_ENV=production115RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nodejs116COPY --from=deps /app/node_modules ./node_modules117COPY --from=build /app/dist ./dist118COPY package.json ./119USER nodejs120EXPOSE 8080121HEALTHCHECK --interval=30s --timeout=5s CMD node -e "fetch('http://localhost:8080/health').then(r=>r.ok?process.exit(0):process.exit(1))"122CMD ["node", "dist/server.js"]123```124125Rules: pin the base image to a major + distro tag (`node:20-bookworm-slim`), run as non-root, copy only what's needed into the runtime stage, expose one port.126127Add a `.dockerignore` that mirrors `.gitignore` plus `node_modules`, `.git`, `*.md`, `tests/`, `coverage/`.128129### 4 — GitHub Actions CI/CD130131Minimum viable for a typical Node app deploying to Fly:132133```yaml134name: deploy135on:136 push:137 branches: [main]138 workflow_dispatch:139140concurrency:141 group: deploy-${{ github.ref }}142 cancel-in-progress: false143144jobs:145 test:146 runs-on: ubuntu-latest147 steps:148 - uses: actions/checkout@v4149 - uses: actions/setup-node@v4150 with:151 node-version: 20152 cache: npm153 - run: npm ci154 - run: npm run lint155 - run: npm test156157 deploy:158 needs: test159 runs-on: ubuntu-latest160 permissions:161 contents: read162 steps:163 - uses: actions/checkout@v4164 - uses: superfly/flyctl-actions/setup-flyctl@master165 - run: flyctl deploy --remote-only166 env:167 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}168```169170Rules: never deploy without tests passing. Use `concurrency` to prevent two deploys racing. Pin action versions to a specific tag (not `@main`).171172### 5 — Secrets & env173174- Never commit `.env`. `.gitignore` it.175- Maintain `.env.example` listing every required variable with a comment about where to get the value.176- For each platform, document the secrets dashboard URL.177- Rotate before launch: any secret that was used in dev or shared in a code review gets rotated before first prod traffic.178179### 6 — First deploy180181Walk through together:1821831. Provision the project on the platform (CLI: `fly launch`, `railway init`, `vercel link`).1842. Set the secrets via the platform's CLI or dashboard.1853. Deploy: `fly deploy` / `railway up` / `vercel --prod`.1864. Hit the health endpoint and the headline URL. Confirm 200.1875. Tail logs for 60 seconds. Confirm no crash-loop, no spammy error.188189### 7 — Document rollback190191Add a `DEPLOY.md` (or section in README) with:192193- Platform + region(s)194- The deploy command195- How to read logs (`fly logs`, `railway logs --follow`, etc.)196- The rollback command (`fly releases` + `fly deploy --image <previous>`, Vercel "Promote to Production" on prior deployment, etc.)197- Who has access to the dashboard198199## Patterns and anti-patterns200201✅ **Do**:202- Pin every base image, every action, every CLI version. "Latest" is a time bomb.203- Health check on a path that exercises the app (`/health` that pings the DB), not just `/`.204- Use the platform's native secrets store; don't reinvent with KMS unless you actually need it.205- Build once, deploy the same artifact to staging then prod (immutable images).206207❌ **Don't**:208- Don't `apt install` random tools in a runtime image. The build stage is for that.209- Don't bake secrets into images. Ever.210- Don't deploy on every push to a feature branch. Trigger on `main` (or after a PR merge).211- Don't set up Kubernetes for a 1-service app. The cost is operational, not just dollar.212- Don't skip the health check thinking "it'll be fine". Without one, the platform routes traffic to a crashing instance.213214## Example invocation215216> User: "I have a Next.js app with a Postgres DB. Ship it. Cheap as possible."2172181. Recommend: Vercel for the Next.js front, Neon for Postgres (generous free tier, branchable for previews). Reasons: zero-config Next.js deploys; Neon's pooled connections work with Vercel serverless.2192. Wire `DATABASE_URL` as a Vercel env var (set for Production + Preview, not Development).2203. Add `vercel.json` only if needed (regions, edge runtime). For most Next.js, defaults are correct.2214. Add `.env.example` with `DATABASE_URL=postgres://...`.2225. GitHub Actions: run lint + typecheck + tests on PRs; Vercel handles the deploy automatically on merge.2236. First deploy: `vercel --prod`. Verify health endpoint, log tail for 60s.2247. Document rollback: Vercel dashboard → previous deployment → Promote to Production.225226## See also227228- `schema-architect` — for the DB migration that must run before/with the first deploy229- `security-sentinel` — sweep for committed secrets before going live230- `git-flow-pro` — set up the protected `main` branch and required checks before connecting CI