How to Configure Docker for Development vs Production Builds in the AI Website Cloner Template

The JCodesMore/ai-website-cloner-template repository ships with a multi-stage production Dockerfile and a lightweight development variant, orchestrated through docker-compose.yml to enable secure, optimized production deployments alongside fast-feedback development workflows.

Configuring Docker correctly ensures your Next.js application runs efficiently in production while remaining flexible during active development. The JCodesMore/ai-website-cloner-template provides distinct containerization strategies for each environment through specialized Dockerfiles and a unified compose configuration. This guide explains how to leverage these files to configure Docker for development vs production builds using the exact implementation found in the source code.

Understanding the Dockerfile Architecture

Production Multi-Stage Build (Dockerfile)

The production configuration uses a multi-stage build process defined in Dockerfile at the repository root. This file implements three distinct stages: dependencies, builder, and runner. The final runner stage copies only compiled assets from .next/standalone and .next/static alongside the public directory, resulting in a minimal attack surface and reduced image size.

Development Single-Stage Build (Dockerfile.dev)

For development workflows, the repository includes Dockerfile.dev, which uses a single stage based on node:24-alpine. This configuration installs dependencies from package.json and package-lock.json but does not compile the application at build time. Instead, it executes npm run dev to enable Next.js hot-reload functionality.

Key Configuration Differences

Production and development builds differ significantly in their architecture and runtime behavior:

  • Build Targets: Production uses target: runner in docker-compose.yml, while development builds the entire Dockerfile.dev without a specific target.
  • Source Code Handling: Production copies source code into the image during build (COPY . .), whereas development mounts the repository as a volume (volumes: - .:/app) for instant reflection of code changes.
  • Environment Variables: Production sets NODE_ENV=production and exposes port 3000, while development uses NODE_ENV=development and maps ${DEV_PORT:-3001}:3000.
  • User Permissions: The production container runs as the non-root node user (USER node), while the development container runs as root to simplify volume permissions.
  • Caching Strategy: Production leverages Docker layer caching and build-cache mounts for node_modules, while development relies on the host's node_modules directory via bind-mount.
  • Health Monitoring: Both configurations include healthchecks using wget -qO- http://localhost:3000/, though the development configuration allows a longer start-up period to accommodate hot-reload initialization.

Building and Running the Production Environment

To create an optimized production image that includes only the compiled application:


# Build the production image targeting the runner stage

docker compose build app

# Start the production container in detached mode

docker compose up -d app

The app service in docker-compose.yml automatically targets the runner stage and exposes port 3000. You can customize the Node.js version by passing a build argument:

docker compose build --build-arg NODE_VERSION=22.10.0-slim app

The default Node version is defined in the first line of Dockerfile as ARG NODE_VERSION=24.14.1-slim.

Building and Running the Development Environment

For active development with hot-reload capabilities:


# Build the development image

docker compose build dev

# Start the development container with volume mounting

docker compose up -d dev

The dev service mounts your local repository to /app inside the container, allowing Next.js to detect file changes immediately without requiring image rebuilds. Rebuild only when modifying package.json or lock files to install new dependencies.

Switching Between Environments

The docker-compose.yml defines both app and dev services, enabling seamless environment switching:


# Run production environment

docker compose up -d app

# Run development environment

docker compose up -d dev

Override default ports using environment variables before running compose commands:

export PORT=8080      # Production port mapping

export DEV_PORT=8081  # Development port mapping

docker compose up -d app

Both services read environment variables from .env or .env.local if present, following the template structure shown in .env.example.

Why Multi-Stage Builds Matter for Production

The three-stage production design in Dockerfile serves specific optimization purposes:

  • Dependencies stage: Installs all node modules once and caches them using --mount=type=cache directives to accelerate subsequent builds.
  • Builder stage: Compiles the Next.js application while keeping build tools and source code separate from the final runtime artifacts.
  • Runner stage: Contains only the compiled application and runtime dependencies, running as a non-root user to minimize security vulnerabilities and image size.

This architecture significantly reduces the final image size compared to including build tools and source code, while the restricted user permissions enhance container security according to the repository's implementation.

Summary

  • The repository provides Dockerfile for production and Dockerfile.dev for development, coordinated through docker-compose.yml.
  • Production builds use a multi-stage process targeting the runner stage, resulting in lean, secure images running as the node user with only .next/standalone and public assets.
  • Development builds mount source code as volumes to enable instant hot-reload without image rebuilds, using npm run dev.
  • Switch environments by specifying either the app or dev service in your docker compose commands.
  • Customize Node.js versions using --build-arg NODE_VERSION when building images, with 24.14.1-slim as the default.

Frequently Asked Questions

How do I change the Node.js version in the Docker containers?

Pass the NODE_VERSION build argument when building either service. For example: docker compose build --build-arg NODE_VERSION=22.10.0-slim app. The default is specified in Dockerfile as ARG NODE_VERSION=24.14.1-slim.

Why does the development container run as root while production uses a non-root user?

The development container runs as root to simplify permissions when bind-mounting the host's source code directory, preventing read/write access issues during hot-reload. The production container uses the node user (USER node) as a security best practice since it doesn't need to write to the filesystem and only serves the pre-built application from .next/standalone.

Do I need to rebuild the development container after every code change?

No. The dev service mounts your local repository as a volume (volumes: - .:/app), so changes reflect immediately via Next.js hot-reload. You only need to rebuild when modifying package.json or package-lock.json to install new dependencies.

Can I run the production build without docker-compose?

Yes. Use docker build with the --target runner flag to build only the final stage: docker build --target runner -t ai-website-cloner:prod . Then run the container: docker run -p 3000:3000 -e NODE_ENV=production ai-website-cloner:prod.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →