Coolify is the deploy environment we run every FH client site on. It’s a self-hosted Heroku alternative — push to GitHub, Coolify rebuilds, Coolify deploys, Coolify manages the reverse proxy. The whole stack costs $20–80/month on a Hetzner or DigitalOcean VPS, beats Vercel’s pricing at scale, and gives us control over the runtime. It’s also where every painful deploy incident in our history has happened. Here’s the configuration that holds up.
Why Coolify (and when not)
We pick Coolify for any client site where (1) the traffic is predictable enough that we don’t need the autoscaling of Vercel, (2) the budget is real enough that $300/month on Vercel adds up, and (3) the team is technical enough to maintain a VPS. That covers most SMB sites we build. For clients with spike traffic, complex serverless needs, or no in-house tech ability, we’ll route them to Vercel and accept the cost.
Dockerfile vs Nixpacks: when to use which
Coolify supports two build paths. Dockerfile gives you full control — you write the Dockerfile, Coolify builds the image and runs it. Nixpacks auto-detects the stack and generates a Dockerfile for you. We use Dockerfile for projects that need anything custom (sharp for image processing, a Python sidecar, custom build steps) and Nixpacks for vanilla Next.js apps where the defaults are fine.
The minimal Next.js Dockerfile
Required: `output: "standalone"` in next.config.ts. Without it, the runtime image carries the entire node_modules (often 5–10× bigger) and start times balloon. With it, the image carries only the files Next actually needs to run.
FROM node:22-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=optional
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]The minimal nixpacks.toml for a subdirectory project
If your Next app lives in a subfolder (fh-site/site, CabCarpentry/cab-app), Nixpacks can’t find it without help. Add a `nixpacks.toml` at the repo root.
[phases.setup]
nixPkgs = ["nodejs_22", "npm"]
[phases.install]
cmds = ["cd site && npm ci"]
[phases.build]
cmds = ["cd site && npm run build"]
[start]
cmd = "cd site && npm start"
[variables]
NODE_OPTIONS = "--max-old-space-size=4096"Memory: size your VPS, set NODE_OPTIONS
Default Coolify VPSes are 1–2GB RAM. `next build` on a Next-16 + React-19 project with 100+ components routinely OOMs there. Size to 4GB minimum. Set `NODE_OPTIONS=--max-old-space-size=4096` in either Dockerfile or nixpacks.toml. We’ve killed three deploys to this exact issue across BHR, fh-site, and CabCarpentry before formalizing the rule in the migration playbook.
Environment variables in Coolify
Coolify’s UI lets you set env vars per-application. NEXT_PUBLIC_-prefixed vars are baked into the build (they’re client-side). Server-only vars (DATABASE_URL, SUPABASE_SERVICE_ROLE_KEY, RESEND_API_KEY) stay server-side. Set them in the Coolify UI, redeploy, and they’re live. Never commit them to git.
If you have NEXT_PUBLIC_ vars that need to change per-deploy, you have to rebuild — they get inlined at build time. This is a Next.js choice, not a Coolify one. If you need runtime env vars on the client, fetch them from a server endpoint instead of inlining.
The reverse proxy and SSL
Coolify uses Caddy or Traefik for the reverse proxy. Both terminate SSL automatically using Let’s Encrypt. You add the domain in Coolify’s UI, point the DNS A record at the VPS IP, wait 30 seconds for the cert to provision, and you’re live. We always set up DNS on Cloudflare for the extra DDoS protection.
Health checks and auto-restart
Coolify pings your app at `/` or a custom health endpoint. If the ping fails, Coolify restarts the container. We add a `/api/health` route that returns a 200 with a brief body — it’s cheaper than the homepage and makes monitoring deterministic.
// app/api/health/route.ts
export async function GET() {
return new Response(JSON.stringify({ ok: true, time: new Date().toISOString() }), {
headers: { "content-type": "application/json" },
});
}Zero-downtime deploys
Coolify does rolling deploys by default — new container starts, health-checks pass, traffic switches, old container stops. Make sure your app starts cleanly without long warmup; we’ve had a couple deploys flap because the new container was slow to read a config file and failed the first health check. The fix is usually to lazy-load whatever was being eagerly fetched at startup.
Logs and observability
Coolify gives you per-container logs in the UI. For anything more — alerting, long retention, structured queries — pipe to a real log destination. We use Better Stack on most sites because it’s cheap and the alerting works. The Coolify logs are fine for ad-hoc debugging but not for actual production monitoring.
The pre-deploy checklist
- `output: "standalone"` in next.config.ts.
- `next` pinned to exact version (no caret).
- No `puppeteer` in dependencies (move to devDeps).
- Exactly one lockfile.
- `reactCompiler` correctly placed (root for Next 16, `experimental` for Next 15).
- `NODE_OPTIONS=--max-old-space-size=4096` set if project has 50+ components.
- Subdirectory projects have a `nixpacks.toml` at repo root.
- Coolify health-check route returns 200.
When Coolify is the wrong choice
Three signals to walk away. (1) Your traffic is spiky — Vercel’s autoscaling will save you more than the Coolify savings on a steady VPS. (2) You need true zero-ops — the client has no one who will SSH into a VPS at 2am. (3) Your app needs serverless features like cron, queues, or background jobs and you don’t want to run them yourself. For SMB marketing sites, none of those apply, and Coolify is the right call.
How this lands on FH client work
Every FH client site currently runs on Coolify. The runtime cost is $20–60/month per VPS, each VPS hosts 1–3 sites comfortably, and the deploy cadence is fast enough that we can ship multiple times a day without thinking about it. If you’re hosting on a slow shared host or paying $200+/month on a CMS platform, book a consultation — moving to a Coolify-backed Next.js stack is one of the highest-ROI changes you can make.