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.
Prerequisites
- 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
Add Docker rules to .cursor/rules
Add Docker rules to .cursor/rules
Create rules that enforce Docker best practices. These prevent Cursor from generating insecure or bloated Dockerfiles.
1---2description: Dockerfile generation rules3globs: "Dockerfile*,docker-compose*.yml"4alwaysApply: true5---67## Dockerfile Rules8- ALWAYS use multi-stage builds (builder + production stages)9- Use node:22-alpine as base, NOT node:22 (saves 700MB+)10- Copy package.json and package-lock.json BEFORE source code (layer caching)11- Run 'npm ci --omit=dev' in production stage (no devDependencies)12- ALWAYS add a non-root USER (node user exists in official images)13- Use COPY --from=builder for production artifacts only14- Set NODE_ENV=production15- Use HEALTHCHECK instruction16- NEVER use 'npm install' — use 'npm ci' for reproducible builds17- Expose the correct port from package.json scriptsExpected result: Cursor generates optimized Dockerfiles following production best practices.
Generate a multi-stage Dockerfile
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.
1// Cursor Chat prompt (Cmd+L):2// @package.json Generate a production Dockerfile for this3// Node.js app. Use multi-stage build with node:22-alpine.4// Stage 1: install all deps and build.5// Stage 2: production-only deps and compiled output.6// Use non-root user, proper layer caching, and HEALTHCHECK.78# Stage 1: Build9FROM node:22-alpine AS builder10WORKDIR /app11COPY package.json package-lock.json ./12RUN npm ci13COPY . .14RUN npm run build1516# Stage 2: Production17FROM node:22-alpine18WORKDIR /app19ENV NODE_ENV=production20COPY package.json package-lock.json ./21RUN npm ci --omit=dev && npm cache clean --force22COPY --from=builder /app/dist ./dist23USER node24EXPOSE 300025HEALTHCHECK --interval=30s CMD wget -q --spider http://localhost:3000/health || exit 126CMD ["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.
Generate a matching .dockerignore
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.
1// Cursor Chat prompt (Cmd+L):2// Generate a .dockerignore for this Node.js project.3// Exclude node_modules, .git, test files, docs, IDE4// configs, and environment files.56node_modules7.git8.gitignore9*.md10!README.md11tests/12__tests__/13coverage/14.env*15.cursor/16.vscode/17dist/18*.logExpected result: A .dockerignore that keeps the build context minimal and secure.
Generate a docker-compose for local development
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.
1// Cursor Chat prompt (Cmd+L):2// @package.json @Dockerfile Generate a docker-compose.yml3// for local development with:4// - App service with volume mount for hot reload5// - PostgreSQL 16 with persistent volume6// - Redis for caching7// - Proper depends_on with healthchecksExpected result: A docker-compose.yml with the app, database, and cache services configured for local development.
Verify the image size and security
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.
1# Build and check size:2docker build -t myapp:latest .3docker images myapp:latest4# Target: under 200MB for a Node.js app56# Check for root user (should show 'node'):7docker run --rm myapp:latest whoami89# Check for no dev dependencies:10docker run --rm myapp:latest npm ls --omit=devExpected result: Image under 200MB, running as non-root user, with no dev dependencies installed.
Complete working example
1# Stage 1: Install dependencies and build2FROM node:22-alpine AS builder3WORKDIR /app45# Copy dependency files first for layer caching6COPY package.json package-lock.json ./7RUN npm ci89# Copy source and build10COPY tsconfig.json ./11COPY src/ ./src/12RUN npm run build1314# Stage 2: Production image15FROM node:22-alpine16WORKDIR /app1718ENV NODE_ENV=production1920# Install production dependencies only21COPY package.json package-lock.json ./22RUN npm ci --omit=dev \23 && npm cache clean --force \24 && rm -rf /tmp/*2526# Copy built artifacts from builder27COPY --from=builder /app/dist ./dist2829# Security: run as non-root user30USER node3132# Expose application port33EXPOSE 30003435# Health check for orchestrators36HEALTHCHECK --interval=30s --timeout=3s --start-period=10s \37 CMD wget -q --spider http://localhost:3000/health || exit 13839# Start the application40CMD ["node", "dist/index.js"]Common mistakes when getting simpler Dockerfiles from Cursor
Why it's a problem: Using node:22 instead of node:22-alpine
How to avoid: Add 'Use node:22-alpine as base' to .cursorrules. Cursor will default to Alpine.
Why it's a problem: Single-stage build that includes dev dependencies
How to avoid: Always request multi-stage builds in your prompt and add the rule to .cursorrules.
Why it's a problem: Running as root user in the container
How to avoid: 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
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Create a production Dockerfile for a Node.js TypeScript app. Use multi-stage build with node:22-alpine. Stage 1: install all deps and build TypeScript. Stage 2: production deps only, copy built artifacts, non-root user, HEALTHCHECK. Also create a .dockerignore and a docker-compose.yml for local dev with PostgreSQL and Redis.
In Cursor Chat (Cmd+L): @package.json @tsconfig.json Generate a production-ready Dockerfile. Use multi-stage build with node:22-alpine. Optimize layer caching (copy package files first). Production stage: npm ci --omit=dev, USER node, HEALTHCHECK. Target image under 200MB.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation