Skip to content

Deploying a Redwood App

Redwood is a full-stack JavaScript/TypeScript framework built for modern web applications. It combines a React frontend and Node.js backend in a monorepo structure, featuring built-in GraphQL API, authentication scaffolding, ORM integration with Prisma, database migrations, and hot module reloading. Redwood provides an opinionated yet flexible architecture that emphasizes developer experience while following best practices from day one. It’s ideal for startups and teams that want to move fast without sacrificing code quality or scalability.

This comprehensive guide walks you through deploying a Redwood application to Klutch.sh, covering both automatic Nixpacks-based deployments and Docker-based deployments. You’ll learn installation steps, explore sample code, configure environment variables, set up your database, and discover best practices for production deployments.

Table of Contents

  • Prerequisites
  • Getting Started: Create a Redwood App
  • Sample Code Examples
  • Project Structure
  • Deploying Without a Dockerfile (Nixpacks)
  • Deploying With a Dockerfile
  • Environment Variables & Configuration
  • Database Setup & Prisma
  • Authentication & Security
  • Troubleshooting
  • Resources

Prerequisites

To deploy a Redwood application on Klutch.sh, ensure you have:

  • Node.js 18 or higher - Redwood requires a modern Node.js version
  • npm or yarn - For managing dependencies (Redwood recommends Yarn)
  • Git - For version control
  • GitHub account - Klutch.sh integrates with GitHub for continuous deployments
  • Klutch.sh account - Sign up for free

Getting Started: Create a Redwood App

Follow these steps to create and set up a new Redwood application:

  1. Create a new Redwood app using the official setup command:
    Terminal window
    npm create redwood-app@latest my-redwood-app
    cd my-redwood-app

    The CLI will prompt you to choose a package manager (yarn or npm). We recommend yarn for Redwood projects.

  2. Install dependencies:
    Terminal window
    yarn install
    # or
    npm install
  3. Start the development server:
    Terminal window
    yarn redwood dev

    Your Redwood app will be available at http://localhost:8910. The development server includes hot module reloading for both frontend and API changes.

  4. Explore the project structure. Redwood creates a monorepo with `/web` (frontend) and `/api` (backend) directories, complete with example pages, GraphQL resolvers, and database models.
  5. To stop the development server, press `Ctrl+C` in your terminal. Before deploying, ensure your app builds successfully locally:
    Terminal window
    yarn redwood build

Sample Code Examples

GraphQL API with Prisma Models

Here’s a complete example of creating a GraphQL API with Redwood and Prisma:

api/src/services/posts/posts.ts
import type { QueryResolvers, MutationResolvers } from 'types/graphql'
import { db } from 'src/lib/db'
export const posts: QueryResolvers['posts'] = () => {
return db.post.findMany({
include: { author: true }
})
}
export const post: QueryResolvers['post'] = ({ id }) => {
return db.post.findUnique({
where: { id },
include: { author: true }
})
}
export const createPost: MutationResolvers['createPost'] = ({ input }) => {
return db.post.create({
data: {
title: input.title,
body: input.body,
authorId: input.authorId
},
include: { author: true }
})
}
export const updatePost: MutationResolvers['updatePost'] = ({ id, input }) => {
return db.post.update({
where: { id },
data: input,
include: { author: true }
})
}
export const deletePost: MutationResolvers['deletePost'] = ({ id }) => {
return db.post.delete({
where: { id }
})
}

React Component with GraphQL Query

web/src/pages/PostsPage/PostsPage.tsx
import { useMutation, useQuery } from '@redwoodjs/web'
import { gql } from '@redwoodjs/web/apollo'
const QUERY = gql`
query GetPosts {
posts {
id
title
body
author {
name
}
}
}
`
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
id
title
body
author {
name
}
}
}
`
const PostsPage = () => {
const { data, loading, error, refetch } = useQuery(QUERY)
const [createPost, { loading: creating }] = useMutation(CREATE_POST, {
onCompleted: () => refetch()
})
const handleCreate = async (title, body) => {
await createPost({
variables: {
input: {
title,
body,
authorId: 1
}
}
})
}
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<h1>Posts</h1>
<button
onClick={() => handleCreate('New Post', 'Post body')}
disabled={creating}
>
{creating ? 'Creating...' : 'Create Post'}
</button>
<ul>
{data?.posts.map(post => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
<p>By {post.author.name}</p>
</li>
))}
</ul>
</div>
)
}
export default PostsPage

Authentication with Redwood

web/src/pages/LoginPage/LoginPage.tsx
import { useAuth } from '@redwoodjs/auth'
import { Form, TextField, PasswordField, SubmitField } from '@redwoodjs/forms'
import { useState } from 'react'
const LoginPage = () => {
const { logIn } = useAuth()
const [error, setError] = useState(null)
const onSubmit = async (data) => {
try {
await logIn({
username: data.email,
password: data.password
})
} catch (err) {
setError(err.message)
}
}
return (
<div>
<h1>Login</h1>
{error && <div className="error">{error}</div>}
<Form onSubmit={onSubmit}>
<TextField
name="email"
placeholder="Email"
validation={{ required: true }}
/>
<PasswordField
name="password"
placeholder="Password"
validation={{ required: true }}
/>
<SubmitField value="Sign In" />
</Form>
</div>
)
}
export default LoginPage

Project Structure

A typical Redwood project has this structure:

my-redwood-app/
├── api/
│ ├── src/
│ │ ├── functions/
│ │ │ └── graphql.ts
│ │ ├── graphql/
│ │ │ ├── posts.sdl.ts
│ │ │ └── users.sdl.ts
│ │ ├── services/
│ │ │ ├── posts/
│ │ │ │ ├── posts.ts
│ │ │ │ └── posts.test.ts
│ │ │ └── users/
│ │ │ └── users.ts
│ │ ├── lib/
│ │ │ └── db.ts
│ │ └── middleware/
│ ├── db/
│ │ ├── schema.prisma
│ │ └── migrations/
│ └── package.json
├── web/
│ ├── src/
│ │ ├── pages/
│ │ │ ├── HomePage/
│ │ │ └── PostsPage/
│ │ ├── components/
│ │ ├── layouts/
│ │ ├── App.tsx
│ │ └── index.tsx
│ └── package.json
├── .env
├── .env.example
├── .redwood
├── redwood.toml
├── package.json
└── yarn.lock or package-lock.json

Deploying Without a Dockerfile

Klutch.sh uses Nixpacks to automatically detect and build your Redwood application. This is the simplest deployment option that requires no additional configuration files.

  1. Test your Redwood app locally to ensure it builds and runs correctly:
    Terminal window
    yarn redwood build
    yarn redwood serve
  2. Push your Redwood application to a GitHub repository with all source code, configuration files, and lock files included.
  3. Log in to your Klutch.sh dashboard.
  4. Create a new project and give it a name (e.g., "My Redwood App").
  5. Create a new app with the following configuration:
    • Repository - Select your Redwood GitHub repository and the branch to deploy
    • Traffic Type - Select HTTP (for web applications serving HTTP traffic)
    • Internal Port - Set to 8910 (the default port for Redwood applications)
    • Region - Choose your preferred region for deployment
    • Compute - Select appropriate compute resources (Redwood apps benefit from adequate memory)
    • Instances - Start with 1 instance for testing, scale as needed
    • Environment Variables - Add any required environment variables (API keys, database URLs, etc.)

    If you need to customize the build or start commands, set these Nixpacks environment variables:

    • BUILD_COMMAND: Override the default build command (e.g., yarn redwood build)
    • START_COMMAND: Override the default start command (e.g., yarn redwood serve)
  6. Click "Create" to deploy. Klutch.sh will automatically detect your Node.js/Redwood project, install dependencies, build both the web and API, and start your application.
  7. Once deployed, your app will be available at a URL like `example-app.klutch.sh`. Test it by visiting the URL in your browser and checking the health status.

Deploying With a Dockerfile

If you prefer more control over the build and runtime environment, you can use a Dockerfile. Klutch.sh will automatically detect and use any Dockerfile in your repository’s root directory.

  1. Create a `Dockerfile` in your project root:
    # Multi-stage build for optimized Redwood deployment
    FROM node:18-bullseye-slim AS builder
    WORKDIR /app
    # Copy package files
    COPY package*.json ./
    COPY yarn.lock* ./
    # Install dependencies
    RUN npm install -g yarn && yarn install --frozen-lockfile
    # Copy source code
    COPY . .
    # Build Redwood app (both web and API)
    RUN yarn redwood build
    # Production stage
    FROM node:18-bullseye-slim
    WORKDIR /app
    # Install runtime dependencies
    RUN npm install -g yarn
    # Copy package files
    COPY package*.json ./
    COPY yarn.lock* ./
    # Install production dependencies only
    RUN yarn install --production --frozen-lockfile
    # Copy built app from builder
    COPY --from=builder /app/dist ./dist
    COPY --from=builder /app/.redwood ./.redwood
    # Set environment variables
    ENV NODE_ENV=production
    # Expose port
    EXPOSE 8910
    # Health check
    HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD node -e "require('http').get('http://localhost:8910/graphql', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
    # Start the application
    CMD ["yarn", "redwood", "serve"]
  2. Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
    node_modules
    npm-debug.log
    yarn-error.log
    .git
    .gitignore
    README.md
    .env
    .env.local
    .vscode
    .idea
    .DS_Store
    dist
    .redwood
  3. Push your code (with Dockerfile and .dockerignore) to GitHub.
  4. Follow the same deployment steps as the Nixpacks method:
    • Log in to Klutch.sh
    • Create a new project
    • Create a new app pointing to your GitHub repository
    • Set the traffic type to HTTP and internal port to 8910
    • Add any required environment variables for your application
    • Click “Create”

    Klutch.sh will automatically detect your Dockerfile and use it to build and deploy your application.

  5. Your deployed app will be available at `example-app.klutch.sh` once the build and deployment complete.

Environment Variables & Configuration

Redwood applications use environment variables for configuration. Set these in the Klutch.sh dashboard during app creation or update them afterward.

Common Environment Variables

# Server configuration
PORT=8910
NODE_ENV=production
LOG_LEVEL=info
# Database
DATABASE_URL=postgresql://user:password@host:5432/redwood_prod
# API configuration
REDWOOD_ENV_API_HOST=https://example-app.klutch.sh
REDWOOD_ENV_API_URL=https://example-app.klutch.sh/graphql
# Authentication (if using Auth0, Firebase, or custom)
AUTH_PROVIDER=dbAuth
AUTH_SECRET=your_secret_key_here
# Session configuration
SESSION_SECRET=your_session_secret_here
# Email service (if needed)
MAIL_SERVICE=sendgrid
SENDGRID_API_KEY=your_sendgrid_key
# Third-party integrations
STRIPE_KEY=your_stripe_key
ALGOLIA_APP_ID=your_algolia_app_id

Using Environment Variables in Your App

api/src/lib/utils.ts
export const getApiUrl = () => {
return process.env.REDWOOD_ENV_API_URL || 'http://localhost:8910/graphql'
}
export const isDevelopment = () => {
return process.env.NODE_ENV === 'development'
}
export const isProduction = () => {
return process.env.NODE_ENV === 'production'
}
web/src/lib/api.ts
export const apiUrl = process.env.REACT_APP_API_URL || '/graphql'
export const getApiHost = () => {
return process.env.REACT_APP_API_HOST || 'http://localhost:8910'
}

Database Setup & Prisma

Redwood includes Prisma ORM for database management. Configure your database connection and run migrations:

Setting Up a PostgreSQL Database

If deploying with a PostgreSQL database on Klutch.sh:

  1. Create a PostgreSQL database on your preferred provider (e.g., Amazon RDS, Supabase, or Neon).
  2. Copy the database connection string and set it as the `DATABASE_URL` environment variable in your Klutch.sh app configuration.
  3. Update your `api/db/schema.prisma` with your models:
    api/db/schema.prisma
    datasource db {
    provider = "postgresql"
    url = env("DATABASE_URL")
    }
    generator client {
    provider = "prisma-client-js"
    }
    model User {
    id Int @id @default(autoincrement())
    email String @unique
    name String?
    posts Post[]
    }
    model Post {
    id Int @id @default(autoincrement())
    title String
    body String
    author User @relation(fields: [authorId], references: [id])
    authorId Int
    }
  4. Create and apply migrations:
    Terminal window
    yarn redwood prisma migrate dev --name init

    This creates a migration and applies it to your local database.

  5. Before deploying, ensure your migrations are committed to Git. When your app deploys on Klutch.sh, you may need to manually run migrations on the production database. Add this to your deployment process:
    Terminal window
    yarn redwood prisma migrate deploy

    Alternatively, automate migrations by adding a startup hook or deployment script.


Authentication & Security

Redwood provides built-in authentication scaffolding. Here’s how to set it up:

DbAuth (Redwood’s Built-in Auth)

api/src/lib/auth.ts
export const getCurrentUser = async (session) => {
if (!session || !session.id) {
return null
}
return await db.user.findUnique({
where: { id: session.id }
})
}
export const isAuthenticated = () => {
return !!getCurrentUser()
}
export const hasRole = (roles) => {
const user = getCurrentUser()
if (!user) return false
return roles.includes(user.role)
}
api/src/graphql/users.sdl.ts
type User {
id: Int!
email: String!
name: String
role: String
posts: [Post!]!
createdAt: DateTime!
updatedAt: DateTime!
}
type Query {
currentUser: User
}
type Mutation {
signUp(input: SignUpInput!): User!
logIn(input: LogInInput!): User!
logOut: Boolean!
}
input SignUpInput {
email: String!
password: String!
name: String
}
input LogInInput {
email: String!
password: String!
}

Troubleshooting

Build Failures

Problem - Deployment fails during the build phase

Solution:

  • Verify your Redwood app builds locally: yarn redwood build
  • Ensure all dependencies are properly listed in package.json
  • Check for TypeScript errors: yarn redwood check
  • Verify database credentials in DATABASE_URL environment variable
  • Check that all API services are properly exported from src/services
  • Review build logs in the Klutch.sh dashboard for specific errors

Application Won’t Start

Problem - App shows as unhealthy after deployment

Solution:

  • Verify the app starts locally: yarn redwood serve
  • Check that port 8910 is configured correctly in Klutch.sh
  • Verify all environment variables are set (especially DATABASE_URL)
  • Ensure database migrations have been applied
  • Check application logs in the Klutch.sh dashboard
  • Verify that the GraphQL endpoint is accessible

Database Connection Issues

Problem - Application crashes with database connection errors

Solution:

  • Verify DATABASE_URL is set and correct
  • Check database credentials and network access
  • Ensure database user has proper permissions
  • Test connection locally before deploying
  • For PostgreSQL, verify the database exists before running migrations
  • Check that migrations have been applied: yarn redwood prisma migrate status
  • Verify database version compatibility (Redwood supports PostgreSQL 11+)

Memory Issues

Problem - App crashes with out of memory errors

Solution:

  • Upgrade to a larger compute tier in Klutch.sh
  • Optimize database queries (add indexes, use pagination)
  • Review API resolver performance
  • Check for memory leaks in custom middleware
  • Monitor memory usage: process.memoryUsage()
  • Consider splitting into multiple services if necessary

Deployment Performance

Problem - App builds slowly or takes a long time to start

Solution:

  • Use the multi-stage Dockerfile to reduce final image size
  • Cache dependencies properly (use node_modules caching)
  • Optimize the number of instances (consider load balancing)
  • Use CDN for static assets
  • Implement database connection pooling for production
  • Enable compression for HTTP responses
  • Consider lazy loading components in the frontend

Resources


Summary

Deploying a Redwood application on Klutch.sh is straightforward whether you choose Nixpacks or Docker. Redwood’s opinionated structure, built-in GraphQL API, and integrated database tooling with Prisma make it easy to build and deploy full-stack applications. Both deployment methods provide reliable, scalable hosting for your Redwood apps. Start with Nixpacks for simplicity, or use Docker for complete control over your build environment. With Redwood’s developer-friendly features and Klutch.sh’s scalable infrastructure, you can move from development to production with confidence, managing both your frontend and backend seamlessly in a single deployment.