Pular para o conteúdo

Buscar na documentação

Buscar na documentação do Skiffly

Next.js + Postgres

A step-by-step tutorial: a Next.js app with Prisma and a Postgres database on Skiffly, from an empty project to a public URL with migrations, using the dashboard, the CLI or an MCP agent.

What you build: one project with two services — web (Next.js 15, App Router, Prisma) and Postgres (from the template) — a generated domain with TLS, migrations that run on every deployment, and a preview environment per pull request. About 15 minutes.

You need: a GitHub account, Node.js 20+, a Next.js app that uses Prisma (or start from npx create-next-app@latest and npx prisma init). Sign in once at app.skiffly.dev.

1. Prepare the app#

Two things in the repository, both ordinary Next.js practice:

package.json
{
  "scripts": {
    "build": "prisma generate && next build",
    "start": "next start",
    "postinstall": "prisma generate"
  }
}
prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Add a health route so rollouts switch traffic only when the new container answers:

app/healthz/route.ts
export function GET() {
  return Response.json({ ok: true });
}

Commit and push. next start reads PORT, so no port code is needed.

2. Create the project and the database#

npm i -g skiffly && skiffly login
cd my-shop
skiffly init --name shop              # project "shop", directory linked
skiffly deploy --template postgres    # service "Postgres", volume, DATABASE_URL

Expected: skiffly status shows Postgres as SUCCESS within a minute.

3. Deploy the app#

skiffly up                       # creates service "my-shop", uploads the directory, builds with Railpack, streams the log

Railpack detects package.json, installs with your lockfile's package manager, runs npm run build and starts npm start. The first build takes 2–4 minutes; the log ends with Deployment SUCCESS.

Rename the service to web if you like: skiffly api 'mutation { serviceUpdate(id: "<id>", input: { name: "web" }) { id } }' or in the dashboard.

The app is built but cannot reach the database yet — that is the next step.

4. Wire the database and run migrations#

skiffly variables set -s web 'DATABASE_URL=${{Postgres.DATABASE_URL}}' NODE_ENV=production

The reference resolves at deploy time to postgresql://postgres:…@postgres:5432/app on the private network. Skiffly has no release phase, so make the start command run migrations before the server:

skiffly api 'mutation { serviceInstanceUpdate(serviceId: "<web id>", environmentId: "<env id>", input: { startCommand: "sh -c \"npx prisma migrate deploy && next start\"", healthcheckPath: "/healthz" }) }'
skiffly redeploy -s web

(skiffly status --json prints the ids.)

Expected in skiffly logs -s web -f: Prisma's All migrations have been successfully applied, then Ready in …. The deployment turns SUCCESS once /healthz answers.

Prefer migrations under your control? Leave the start command as next start and run skiffly ssh -s web -- npx prisma migrate deploy after each deploy that ships a migration.

5. Get a URL#

skiffly domain -s web         # https://web-x1y2.skiffly.cloud

Open it. For app.example.com: skiffly domain app.example.com, create the CNAME and TXT records it prints, skiffly domain status app.example.com until verified. Set AUTH_URL/NEXTAUTH_URL and similar to the final URL — https://${{self.SKIFFLY_PUBLIC_DOMAIN}} follows the generated domain automatically.

6. Look inside the database#

skiffly connect postgres      # opens psql through a temporary TCP proxy
\dt

skiffly proxy delete <port> closes the public proxy afterwards.

7. Previews per pull request#

Project → Settings → PR previews (or projectUpdate(id, input: { prDeploys: true })). Every PR gets a pr-<n> environment: a copy of the variables, web built from the PR branch, and its own empty Postgres (data is not copied; prisma migrate deploy in the start command brings the schema up). The environment is deleted when the PR closes.

8. Same thing as code#

Adopt what you built into a file and keep it in the repository:

skiffly config init            # writes .skiffly/skiffly.ts from the environment
.skiffly/skiffly.ts
import { defineSkiffly, github, postgres, project, service } from "@skiffly/config";
 
export default defineSkiffly(() => {
  const db = postgres("Postgres");
  const web = service("web", {
    source: github("acme/my-shop"),
    start: 'sh -c "npx prisma migrate deploy && next start"',
    healthcheck: "/healthz",
    port: 3000,
    env: { NODE_ENV: "production", DATABASE_URL: db.env.DATABASE_URL },
    domain: true,
  });
  return project("shop", { resources: [db, web] });
});

skiffly config plan shows no changes; from now on skiffly config apply in CI reconciles settings.

Troubleshooting#

SymptomFix
Build fails with @prisma/client did not initialize yetprisma generate missing from postinstall/build
P1001: Can't reach database server at postgres:5432the reference is on the wrong service, or Postgres is still starting: skiffly status
Deployment stays DEPLOYING then FAILED-p 3000 hardcoded in start while the service port is 8080; drop the flag or set port 3000
NEXT_PUBLIC_* is undefined in the browserset the variable, then a new build (skiffly up)

Next: Next.js guide · Variables · Deployments