# How to get simpler Dockerfiles from Cursor

- Tool: Cursor
- Difficulty: Intermediate
- Time required: 10-15 min
- Compatibility: Cursor Free+, Node.js projects with Docker
- Last updated: March 2026

## TL;DR

Cursor generates bloated Dockerfiles with unnecessary layers, missing multi-stage builds, and root user execution. By adding Docker rules to .cursorrules and referencing your package.json when prompting, you get production-ready Dockerfiles with multi-stage builds, proper caching, non-root users, and minimal image sizes under 200MB.

## Getting simpler Dockerfiles from Cursor

Cursor often generates single-stage Dockerfiles that copy everything, install dev dependencies in production, and run as root. This tutorial shows how to get minimal, secure, multi-stage Dockerfiles that follow production best practices.

## Before you start

- Cursor installed with a Node.js project
- Docker installed locally
- A package.json with defined scripts
- Familiarity with Cursor Chat (Cmd+L)

## Step-by-step guide

### 1. Add Docker rules to .cursor/rules

Create rules that enforce Docker best practices. These prevent Cursor from generating insecure or bloated Dockerfiles.

```
---
description: Dockerfile generation rules
globs: "Dockerfile*,docker-compose*.yml"
alwaysApply: true
---

## Dockerfile Rules
- ALWAYS use multi-stage builds (builder + production stages)
- Use node:22-alpine as base, NOT node:22 (saves 700MB+)
- Copy package.json and package-lock.json BEFORE source code (layer caching)
- Run 'npm ci --omit=dev' in production stage (no devDependencies)
- ALWAYS add a non-root USER (node user exists in official images)
- Use COPY --from=builder for production artifacts only
- Set NODE_ENV=production
- Use HEALTHCHECK instruction
- NEVER use 'npm install' — use 'npm ci' for reproducible builds
- Expose the correct port from package.json scripts
```

**Expected result:** Cursor generates optimized Dockerfiles following production best practices.

### 2. Generate a multi-stage Dockerfile

Ask Cursor to create a Dockerfile referencing your package.json so it knows the correct build commands, entry point, and port.

```
// Cursor Chat prompt (Cmd+L):
// @package.json Generate a production Dockerfile for this
// Node.js app. Use multi-stage build with node:22-alpine.
// Stage 1: install all deps and build.
// Stage 2: production-only deps and compiled output.
// Use non-root user, proper layer caching, and HEALTHCHECK.

# Stage 1: Build
FROM node:22-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

# Stage 2: Production
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY package.json package-lock.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=builder /app/dist ./dist
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s CMD wget -q --spider http://localhost:3000/health || exit 1
CMD ["node", "dist/index.js"]
```

> Pro tip: Copying package.json before source code means Docker caches the npm ci layer. Source code changes do not trigger a full dependency reinstall.

**Expected result:** A multi-stage Dockerfile under 20 lines with optimized layer caching and security.

### 3. Generate a matching .dockerignore

Ask Cursor to create a .dockerignore that prevents unnecessary files from entering the build context. This reduces build time and prevents secrets from leaking into images.

```
// Cursor Chat prompt (Cmd+L):
// Generate a .dockerignore for this Node.js project.
// Exclude node_modules, .git, test files, docs, IDE
// configs, and environment files.

node_modules
.git
.gitignore
*.md
!README.md
tests/
__tests__/
coverage/
.env*
.cursor/
.vscode/
dist/
*.log
```

**Expected result:** A .dockerignore that keeps the build context minimal and secure.

### 4. Generate a docker-compose for local development

Ask Cursor to create a docker-compose.yml for local development that mounts source code, enables hot reload, and includes common services like PostgreSQL and Redis.

```
// Cursor Chat prompt (Cmd+L):
// @package.json @Dockerfile Generate a docker-compose.yml
// for local development with:
// - App service with volume mount for hot reload
// - PostgreSQL 16 with persistent volume
// - Redis for caching
// - Proper depends_on with healthchecks
```

**Expected result:** A docker-compose.yml with the app, database, and cache services configured for local development.

### 5. Verify the image size and security

Build the Docker image and verify it meets size and security targets. Use Cursor's terminal to run the build and inspection commands.

```
# Build and check size:
docker build -t myapp:latest .
docker images myapp:latest
# Target: under 200MB for a Node.js app

# Check for root user (should show 'node'):
docker run --rm myapp:latest whoami

# Check for no dev dependencies:
docker run --rm myapp:latest npm ls --omit=dev
```

**Expected result:** Image under 200MB, running as non-root user, with no dev dependencies installed.

## Complete code example

File: `Dockerfile`

```dockerfile
# Stage 1: Install dependencies and build
FROM node:22-alpine AS builder
WORKDIR /app

# Copy dependency files first for layer caching
COPY package.json package-lock.json ./
RUN npm ci

# Copy source and build
COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# Stage 2: Production image
FROM node:22-alpine
WORKDIR /app

ENV NODE_ENV=production

# Install production dependencies only
COPY package.json package-lock.json ./
RUN npm ci --omit=dev \
  && npm cache clean --force \
  && rm -rf /tmp/*

# Copy built artifacts from builder
COPY --from=builder /app/dist ./dist

# Security: run as non-root user
USER node

# Expose application port
EXPOSE 3000

# Health check for orchestrators
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \
  CMD wget -q --spider http://localhost:3000/health || exit 1

# Start the application
CMD ["node", "dist/index.js"]
```

## Common mistakes

- **Using node:22 instead of node:22-alpine** — The full Node.js image is 900MB+. Alpine-based images are 100-150MB. Cursor often uses the full image because it appears more frequently in training data. Fix: Add 'Use node:22-alpine as base' to .cursorrules. Cursor will default to Alpine.
- **Single-stage build that includes dev dependencies** — Without multi-stage builds, devDependencies (TypeScript, testing tools) end up in the production image, bloating it and creating security risks. Fix: Always request multi-stage builds in your prompt and add the rule to .cursorrules.
- **Running as root user in the container** — Cursor forgets the USER instruction. Running as root means any container escape gives full host access. Fix: Add 'ALWAYS add USER node instruction' to your Docker rules. The node user exists by default in official Node.js images.

## Best practices

- Always use multi-stage builds to separate build and production stages
- Use Alpine-based images to reduce image size by 80%+
- Copy package.json before source code for optimal layer caching
- Use npm ci instead of npm install for reproducible builds
- Run containers as non-root user (USER node)
- Add HEALTHCHECK for container orchestrator integration
- Generate .dockerignore to keep build context minimal

## Frequently asked questions

### Why does Cursor generate such large Dockerfiles?

Cursor's training data includes many tutorial-level Dockerfiles that prioritize simplicity over optimization. Adding Docker-specific rules and requesting multi-stage builds produces much smaller images.

### Should I use distroless instead of Alpine?

Distroless images are even smaller and more secure but lack a shell for debugging. Use Alpine for most projects and distroless for high-security production containers. Specify your preference in the prompt.

### Can Cursor generate Dockerfiles for monorepos?

Yes. Reference the specific package's package.json and specify the build context. Cursor can generate Dockerfiles that build only the needed package from a monorepo.

### How do I handle environment variables in Docker?

Use ARG for build-time variables and ENV for runtime. Never bake secrets into the image. Ask Cursor to use environment variables for all configuration.

### Can Cursor Agent run Docker commands?

Yes, in Agent mode with terminal access enabled. However, for Docker builds that take minutes, it is better to run them manually or through CI/CD.

---

Source: https://www.rapidevelopers.com/cursor-tutorial/how-to-tell-cursor-ai-to-produce-minimal-maintainable-dockerfiles-for-node-js-apps
© RapidDev — https://www.rapidevelopers.com/cursor-tutorial/how-to-tell-cursor-ai-to-produce-minimal-maintainable-dockerfiles-for-node-js-apps
