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: runnerindocker-compose.yml, while development builds the entireDockerfile.devwithout 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=productionand exposes port3000, while development usesNODE_ENV=developmentand maps${DEV_PORT:-3001}:3000. - User Permissions: The production container runs as the non-root
nodeuser (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'snode_modulesdirectory 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=cachedirectives 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
Dockerfilefor production andDockerfile.devfor development, coordinated throughdocker-compose.yml. - Production builds use a multi-stage process targeting the
runnerstage, resulting in lean, secure images running as thenodeuser with only.next/standaloneandpublicassets. - 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
appordevservice in your docker compose commands. - Customize Node.js versions using
--build-arg NODE_VERSIONwhen building images, with24.14.1-slimas 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →