Back to Blog
Tutorial11 min read2026-07-05

How to Deploy a RedwoodJS App with a Database

RedwoodJS splits into an API side and a web side, each with its own build output. This guide covers building both, running Prisma migrations, and deploying the GraphQL API plus the static frontend correctly.

Ajay Kumar
Ajay Kumar
Founder & DevOps, PandaStack

RedwoodJS is an opinionated full-stack framework: React on the frontend, a GraphQL API on the backend, Prisma for the database, all in one monorepo. Its defining trait for deployment is the side splitapi and web build into separate artifacts that you can host together or apart. Understanding that split is the whole game.

The two sides

A Redwood project has two workspaces:

  • api — a GraphQL server (Apollo/Yoga) backed by Prisma. This is a long-running Node service.
  • web — a React app that builds to static files and calls the API's GraphQL endpoint.

This maps perfectly onto the "static frontend + API service" pattern. The web side can be served from a CDN; the api side runs as a container.

Building both sides

Redwood's CLI builds everything:

yarn rw build         # builds both api and web
# or individually:
yarn rw build api
yarn rw build web

This produces:

  • api/dist — the compiled GraphQL server.
  • web/dist — static HTML/JS/CSS for the frontend.

Prisma migrations

Redwood uses Prisma, so production migrations use prisma migrate deploy via Redwood's CLI:

# In development, create migrations:
yarn rw prisma migrate dev --name add_posts

# In production, apply existing migrations:
yarn rw prisma migrate deploy

Like all Prisma migrate deploy, this only applies committed migrations and never prompts — exactly what you want on a server. Run it as a deploy step before the API serves traffic. Set DATABASE_URL in the environment; Prisma reads it from schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

Serving the API

Redwood ships a server for the api side:

yarn rw serve api --port 8911

For a container, that becomes your start command. The web side, meanwhile, needs to know where the API lives — configured at build time via redwood.toml and the apiUrl:

# redwood.toml
[web]
  apiUrl = "https://api.example.com"
[api]
  port = 8911

Set apiUrl to the deployed API origin before building web, so the static bundle points at the right backend.

Two deploy shapes

Shape 1 — Serve both from one container. yarn rw serve serves the API and the built web files together. Simplest, one service:

FROM node:20-slim AS build
WORKDIR /app
COPY . .
RUN yarn install --frozen-lockfile && yarn rw build

FROM node:20-slim
WORKDIR /app
COPY --from=build /app ./
ENV NODE_ENV=production
EXPOSE 8910
CMD ["yarn", "rw", "serve", "--port", "8910"]

Shape 2 — Split. Serve web/dist from a CDN-backed static host and run the api side as its own container. Better caching and independent scaling.

Deploying on PandaStack

Combined (Shape 1)

  1. 1Provision managed PostgreSQL; DATABASE_URL is injected.
  2. 2Deploy the repo as a container app running yarn rw serve.
  3. 3Run yarn rw prisma migrate deploy once before traffic.
  4. 4Attach a domain; SSL is automatic.

Split (Shape 2)

  1. 1Deploy the api workspace as a container app (yarn rw serve api). DATABASE_URL injected; run migrations as a deploy step.
  2. 2Set apiUrl in redwood.toml to the API's URL, then deploy web as a static site — PandaStack auto-detects the build and serves web/dist from its CDN-backed static hosting.
  3. 3Both get custom domains and automatic SSL.

The split shape is the natural fit: Redwood's clean api/web boundary lines up exactly with PandaStack's separate container and static product types, so the frontend is cheap to serve and the API scales on its own.

Production checklist

  • [ ] prisma migrate deploy run as a deploy step (never auto-migrate).
  • [ ] apiUrl set correctly before building web.
  • [ ] NODE_ENV=production.
  • [ ] CORS allows the web origin if api and web are on different domains.
  • [ ] Connection pool sized to your DB instance.

Verifying

# GraphQL health
curl -s -X POST https://api.example.com/graphql \
  -H 'content-type: application/json' \
  -d '{"query":"{ __typename }"}'

# Static frontend loads
curl -s -o /dev/null -w '%{http_code}' https://app.example.com/

A valid GraphQL response and a 200 on the frontend confirm both sides are wired.

References

  • [RedwoodJS deployment docs](https://redwoodjs.com/docs/deploy/introduction)
  • [Redwood CLI (build, serve, prisma)](https://redwoodjs.com/docs/cli-commands)
  • [redwood.toml configuration](https://redwoodjs.com/docs/app-configuration-redwood-toml)
  • [Prisma migrate in production](https://www.prisma.io/docs/orm/prisma-migrate/workflows/development-and-production)

---

Redwood's api/web split maps directly onto PandaStack's container apps and static sites, with a managed PostgreSQL and DATABASE_URL injected. Try it free at [dashboard.pandastack.io](https://dashboard.pandastack.io).

Ready to deploy?

Start free on PandaStack.

Start free on PandaStack

More in Tutorial

Browse all Tutorial articles →

See also