Skip to main content
Turborepo is a framework-agnostic tool for efficiently managing monorepos. It includes the following features:
  • Turborepo build system - Intelligent build orchestration with incremental builds.
  • Parallel execution - Run tasks across multiple packages simultaneously with automatic CPU optimization.
  • Task pipelines - Define dependencies between tasks to ensure optimal execution order.
  • Workspace management - Organize multiple packages and apps in a single repository.
  • Incremental adoption - Add Turborepo to existing monorepos without refactoring.
  • Selective task execution - Run tasks only for packages affected by your changes.
  • TypeScript support - Full TypeScript configuration across all workspaces.
  • Framework agnostic - Works with Next.js, React, Vue, Express, and any JavaScript/TypeScript framework.

Configuration

In Sevalla, each application instance can host only a single app. Therefore, in Monorepos with workspace dependencies (packages/*), your services must be built from the repository root directory to access all workspace packages and ensure all dependencies resolve correctly.
To do this, all you need to do is ensure that your build and start commands are specific to the service you wish to deploy. Refer to our examples in the containerization section for guidance on how to achieve this.

Environment variable types

  • globalEnv: Variables that apply to every task and invalidate the cache when changed. Use these for settings that impact all builds, such asNODE_ENV, DATABASE_URL.
  • Task-specific env: Variables scoped to individual tasks only. These help avoid unnecessary cache invalidation by limiting their impact to the tasks that actually use them.
  • Framework variables: Variables like NEXT_PUBLIC_*, VITE_*, or REACT_APP_* are automatically detected by Turborepo and will invalidate the cache when modified. Use these for client-exposed configuration.
  • passThroughEnv: Variables that are passed to tasks but do not affect cache keys. Ideal for runtime-only values that should not trigger new builds, such as secrets or tokens that don’t alter the build output.

Containerization

Dockerfile

You must create separate Dockerfiles for each application, the web frontend, and the API backend, located in the repository’s root directory. After creating each application in Sevalla, assign the correct Dockerfile to the corresponding service within Applications > application name > Settings > Update build strategy. Inside each Dockerfile, make sure to use the appropriate build and start commands defined in that application’s package.json. Example Dockerfile for the web frontend:
# Dockerfile for Next.js Web App
# Production-ready build for deploying web app separately

FROM node:lts-alpine AS base

# Enable pnpm
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate

# Builder stage - install dependencies and build
FROM base AS builder

WORKDIR /app

# Copy root package files
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./

# Copy all package.json files to install workspace dependencies
COPY apps/web/package.json ./apps/web/package.json
COPY apps/api/package.json ./apps/api/package.json
COPY packages/ui/package.json ./packages/ui/package.json
COPY packages/typescript-config/package.json ./packages/typescript-config/package.json

# Install all dependencies (needed for workspace)
RUN pnpm install

# Copy source files
COPY . .

# Build web app and its dependencies using Turborepo
RUN pnpm build:web

# Runner stage - minimal production image
FROM base AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3000

# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

# Copy Next.js standalone build
# The standalone output includes all dependencies needed to run
# (No need to copy node_modules - standalone bundles everything)
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public

USER nextjs

EXPOSE 3000

# Start Next.js standalone server
CMD ["node", "apps/web/server.js"]
Example Dockerfile for the API backend:
# Dockerfile for Express API
# Production-ready build for deploying API separately

FROM node:lts-alpine AS base

# Enable pnpm
RUN corepack enable && corepack prepare pnpm@9.15.4 --activate

# Builder stage - install dependencies and build
FROM base AS builder

WORKDIR /app

# Copy root package files
COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml ./

# Copy all package.json files to install workspace dependencies
COPY apps/web/package.json ./apps/web/package.json
COPY apps/api/package.json ./apps/api/package.json
COPY packages/ui/package.json ./packages/ui/package.json
COPY packages/typescript-config/package.json ./packages/typescript-config/package.json

# Install all dependencies (needed for workspace)
RUN pnpm install

# Copy source files
COPY . .

# Build API using Turborepo
RUN pnpm build:api

# Runner stage - minimal production image
FROM base AS runner

WORKDIR /app

ENV NODE_ENV=production
ENV PORT=3001

# Create non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 apiuser

# Copy built API
COPY --from=builder --chown=apiuser:nodejs /app/apps/api/dist ./dist
COPY --from=builder --chown=apiuser:nodejs /app/apps/api/package.json ./package.json

# Copy only production node_modules from builder
# This avoids workspace resolution issues and is more efficient
COPY --from=builder --chown=apiuser:nodejs /app/node_modules ./node_modules

USER apiuser

EXPOSE 3001

# Start Express API
CMD ["node", "dist/index.js"]

Nixpacks

You must create a separate nixpacks.toml file for each application, the web frontend, and the API backend, located in the repository’s root directory. Alternatively, you can use the following Nixpacks environment variables to specify the build and start commands:
  • NIXPACKS_BUILD_CMD
  • NIXPACKS_START_CMD
You can control the Node.js version used during the build by using an .nvmrc file, the engines field in package.json, or the NIXPACKS_NODE_VERSION environment variable. If none are set, Nixpacks defaults to Node.js 18. Example nixpacks.toml file for the web frontend:
[phases.build]
cmds = [
  "pnpm build:web"
]

[start]
cmd = "pnpm start:web"
Example nixpacks.toml file for the API backend:
[phases.build]
cmds = [
  "pnpm build:api"
]

[start]
cmd = "pnpm start:api"