Step 4
Step 4 — Deploy (Vercel · Fly.io · Docker)
0 views
Step 4 — Deploy (Vercel · Fly.io · Docker)
Three practical paths for a Next.js fullstack.
1. Three options
| Path | Pros | Cons |
|---|---|---|
| Vercel | easiest · git-push deploy · free tier | serverless limits · long tasks hard |
| Fly.io | Dockerfile as-is · Postgres included · always-on | slightly more setup |
| Self-host + Docker + Caddy | full control · cheapest | ops overhead |
Beginners → Vercel. Intermediate → Fly.io. Educational → self-host.
2. Vercel
- Sign up at vercel.com
- Add New... → Project
- Pick the GitHub repo
- Framework auto-detects Next.js
- Set env vars (
DATABASE_URLetc.) - Deploy
Main push → auto redeploy. PRs get preview URLs.
Limits: 10s function timeout (Pro 60s / Enterprise 900s), serverless only, external Postgres (Neon / Supabase).
3. Fly.io — Docker-based
standalone build
next.config.ts:
import type { NextConfig } from "next";
const config: NextConfig = { output: "standalone" };
export default config;
Dockerfile
FROM node:20-alpine AS base
RUN corepack enable && corepack prepare pnpm@10.33.0 --activate
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production ENV PORT=3000
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
USER node
EXPOSE 3000
CMD ["node", "server.js"]
.dockerignore:
node_modules
.next
.git
.env.local
Dockerfile
README.md
CLI
brew install flyctl # or winget install flyctl
fly auth signup
fly launch
fly postgres create
fly postgres attach <app-name>
fly deploy
Live at https://<app>.fly.dev.
Handy commands
fly status
fly logs
fly ssh console
fly secrets set KEY=VALUE
fly scale count 2
4. Self-host + Caddy
docker-compose.yml:
services:
web:
build: .
env_file: .env.prod
ports: ["127.0.0.1:3000:3000"]
depends_on: [postgres]
postgres:
image: postgres:16-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: my_app
volumes: [pg-data:/var/lib/postgresql/data]
ports: ["127.0.0.1:5432:5432"]
caddy:
image: caddy:2-alpine
ports: ["80:80", "443:443"]
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
volumes:
pg-data:
caddy-data:
Caddyfile:
example.com {
reverse_proxy web:3000
}
Deploy:
scp -r . user@server:/opt/my-fullstack
ssh user@server
cd /opt/my-fullstack
docker compose up -d
5. Env var separation
| Var | Purpose |
|---|---|
DATABASE_URL |
production DB |
NEXT_PUBLIC_SITE_URL |
client-exposed URL |
NEXTAUTH_SECRET / JWT_SECRET |
session |
STRIPE_*, RESEND_* |
external APIs |
Vercel → Dashboard · Fly.io → fly secrets · self-host → .env.prod.
6. Custom domain
Vercel and Fly.io both support custom domains. DuckDNS works fine for learning.
7. HTTPS
- Vercel / Fly.io — automatic
- Caddy — automatic
- Nginx + certbot — manual
Don't run without HTTPS; cookies and OAuth often require it.
8. Observability
fly logs
vercel logs
docker compose logs -f web
Add Sentry when errors stick.
9. CI/CD
name: deploy
on: { push: { branches: [main] } }
jobs:
deploy:
runs-on: ubuntu-latest
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 }}" }
Vercel auto-deploys via git integration.
10. Gotchas
- Build fails for missing env — check build-time vs runtime
- Migrations not applied —
fly ssh consoleand runpnpm drizzle-kit migrate - Image too large — check
output: "standalone"and.dockerignore - HTTPS cert failure — DNS must resolve first; Caddy needs port 80 open
11. Costs
| Option | Free | Paid |
|---|---|---|
| Vercel | Hobby (personal, non-commercial) | $20/mo Pro |
| Fly.io | $5 credit / small apps | per-instance billing |
| VPS | none | $4–10/mo (Hetzner) |
12. Post-deploy checklist
-
/api/healthand home 200 - Valid HTTPS cert
- DB migrations applied
- No missing env
- Logging / error monitoring set
- Production secrets separated from test
Closing
A successful first deployment is a milestone. Start on Vercel in 5 minutes, graduate to Fly.io or self-host. Code that's actually deployed is code you've actually learned.
🎉 Fullstack cycle complete.
Next
- devops-cloud or security-foundations
🎉 You finished Build Your First Fullstack App with Next.js 16
What's next? Pick another course below.