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:
-
Create a new Redwood app using the official setup command:
Terminal window npm create redwood-app@latest my-redwood-appcd my-redwood-appThe CLI will prompt you to choose a package manager (yarn or npm). We recommend yarn for Redwood projects.
-
Install dependencies:
Terminal window yarn install# ornpm install -
Start the development server:
Terminal window yarn redwood devYour Redwood app will be available at http://localhost:8910. The development server includes hot module reloading for both frontend and API changes.
- Explore the project structure. Redwood creates a monorepo with `/web` (frontend) and `/api` (backend) directories, complete with example pages, GraphQL resolvers, and database models.
-
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:
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
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 PostsPageAuthentication with Redwood
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 LoginPageProject 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.jsonDeploying 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.
-
Test your Redwood app locally to ensure it builds and runs correctly:
Terminal window yarn redwood buildyarn redwood serve - Push your Redwood application to a GitHub repository with all source code, configuration files, and lock files included.
- Log in to your Klutch.sh dashboard.
- Create a new project and give it a name (e.g., "My Redwood App").
-
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)
- 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.
- 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.
-
Create a `Dockerfile` in your project root:
# Multi-stage build for optimized Redwood deploymentFROM node:18-bullseye-slim AS builderWORKDIR /app# Copy package filesCOPY package*.json ./COPY yarn.lock* ./# Install dependenciesRUN npm install -g yarn && yarn install --frozen-lockfile# Copy source codeCOPY . .# Build Redwood app (both web and API)RUN yarn redwood build# Production stageFROM node:18-bullseye-slimWORKDIR /app# Install runtime dependenciesRUN npm install -g yarn# Copy package filesCOPY package*.json ./COPY yarn.lock* ./# Install production dependencies onlyRUN yarn install --production --frozen-lockfile# Copy built app from builderCOPY --from=builder /app/dist ./distCOPY --from=builder /app/.redwood ./.redwood# Set environment variablesENV NODE_ENV=production# Expose portEXPOSE 8910# Health checkHEALTHCHECK --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 applicationCMD ["yarn", "redwood", "serve"]
-
Create a `.dockerignore` file to exclude unnecessary files from the Docker build:
node_modulesnpm-debug.logyarn-error.log.git.gitignoreREADME.md.env.env.local.vscode.idea.DS_Storedist.redwood
- Push your code (with Dockerfile and .dockerignore) to GitHub.
-
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.
- 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 configurationPORT=8910NODE_ENV=productionLOG_LEVEL=info
# DatabaseDATABASE_URL=postgresql://user:password@host:5432/redwood_prod
# API configurationREDWOOD_ENV_API_HOST=https://example-app.klutch.shREDWOOD_ENV_API_URL=https://example-app.klutch.sh/graphql
# Authentication (if using Auth0, Firebase, or custom)AUTH_PROVIDER=dbAuthAUTH_SECRET=your_secret_key_here
# Session configurationSESSION_SECRET=your_session_secret_here
# Email service (if needed)MAIL_SERVICE=sendgridSENDGRID_API_KEY=your_sendgrid_key
# Third-party integrationsSTRIPE_KEY=your_stripe_keyALGOLIA_APP_ID=your_algolia_app_idUsing Environment Variables in Your App
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'}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:
- Create a PostgreSQL database on your preferred provider (e.g., Amazon RDS, Supabase, or Neon).
- Copy the database connection string and set it as the `DATABASE_URL` environment variable in your Klutch.sh app configuration.
-
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 @uniquename String?posts Post[]}model Post {id Int @id @default(autoincrement())title Stringbody Stringauthor User @relation(fields: [authorId], references: [id])authorId Int} -
Create and apply migrations:
Terminal window yarn redwood prisma migrate dev --name initThis creates a migration and applies it to your local database.
-
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 deployAlternatively, 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)
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)}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_URLenvironment 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_URLis 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_modulescaching) - 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
- Redwood Official Website
- Redwood Documentation
- Redwood GraphQL Guide
- Redwood Authentication
- Prisma ORM Documentation
- Nixpacks Documentation
- Klutch.sh Dashboard
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.