Docker
Multi-stage builds and docker-compose.
When you enable Docker support, PEST.js generates a Dockerfile, docker-compose.yml, and .dockerignore. The Dockerfile uses multi-stage builds for small production images, and the install commands match your chosen package manager.
Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]FROM node:20-alpine AS builder
RUN corepack enable
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm run build
FROM node:20-alpine AS runner
RUN corepack enable
WORKDIR /app
ENV NODE_ENV=production
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile --prod
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
COPY . .
RUN yarn build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile --production
COPY --from=builder /app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/server.js"]Prisma projects get an extra prisma generate step in the build stage. The generated client, the prisma/ directory, and prisma.config.ts are all copied into the production image.
docker-compose.yml
The compose file always includes an app service. When you select PostgreSQL or MySQL, a db service is added with health checks. The app waits for the database to be healthy before starting.
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
environment:
- DATABASE_URL=postgresql://user:password@db:5432/mydb
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
POSTGRES_DB: mydb
volumes:
- db_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 5s
timeout: 5s
retries: 5
volumes:
db_data:services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
environment:
- DATABASE_URL=mysql://user:password@db:3306/mydb
depends_on:
db:
condition: service_healthy
db:
image: mysql:8
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_USER: user
MYSQL_PASSWORD: password
MYSQL_DATABASE: mydb
volumes:
- db_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 5
volumes:
db_data:SQLite is file-based, so no database service is needed. A volume mount persists the database file across container restarts:
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
environment:
DATABASE_URL: "/app/data/dev.db"
volumes:
- ./data:/app/dataThe DATABASE_URL in the compose file uses db as the hostname instead of localhost because containers communicate over Docker's internal network.
.dockerignore
node_modules
dist
.env
.git
coverage
*.log
.DS_StoreRunning
docker compose up --buildYour API will be available at http://localhost:3000.