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 split — api 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 webThis 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 deployLike 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 8911For 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 = 8911Set 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)
- 1Provision managed PostgreSQL;
DATABASE_URLis injected. - 2Deploy the repo as a container app running
yarn rw serve. - 3Run
yarn rw prisma migrate deployonce before traffic. - 4Attach a domain; SSL is automatic.
Split (Shape 2)
- 1Deploy the api workspace as a container app (
yarn rw serve api).DATABASE_URLinjected; run migrations as a deploy step. - 2Set
apiUrlinredwood.tomlto the API's URL, then deploywebas a static site — PandaStack auto-detects the build and servesweb/distfrom its CDN-backed static hosting. - 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 deployrun as a deploy step (never auto-migrate). - [ ]
apiUrlset correctly before buildingweb. - [ ]
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).