Configure Your Environment
Best Practices
Generated Output Dockerfile

    
Key Terms Explained
Dockerfile
A plain text file that contains a sequence of instructions Docker reads top to bottom to assemble a container image. Every instruction creates a new layer in the image.
Image vs. Container
An image is a read-only snapshot built from a Dockerfile. A container is a running instance of that image with an added writable layer. One image can spawn many simultaneous containers.
Multi-Stage Build
A Dockerfile technique that uses multiple FROM instructions to keep build tools in an early stage and copy only the finished artifacts into a lean final image, slashing image size.
Alpine Linux
A security-oriented, minimal Linux distribution that weighs under 5 MB. Used as a base for Docker images to reduce size and attack surface. Uses musl libc instead of glibc.
Layer Caching
Docker reuses unchanged layers from a previous build. Ordering instructions so slow steps (like npm install) come before frequently changing files (like source code) keeps builds fast.
ENTRYPOINT vs. CMD
ENTRYPOINT sets the fixed executable that always runs. CMD sets its default arguments, which can be overridden at runtime. Together they define what the container does when started.
Rootless Container
A container whose main process runs as a non-root user, created with addgroup/adduser and the USER directive. Limits the blast radius if an attacker gains code execution inside the container.
WORKDIR
Sets the working directory for all subsequent RUN, COPY, ADD, CMD, and ENTRYPOINT instructions. Creates the directory if it does not exist and avoids fragile absolute-path assumptions.

The Complete Guide to Writing Production-Ready Dockerfiles

A working Dockerfile that just runs your app is easy to write. A Dockerfile that produces a small, secure, and cache-efficient image takes a little more thought. This guide walks through the decisions this tool makes for you and explains the reasoning behind each one.

How to Use This Tool

Select your runtime from the Environment dropdown. The Base Image Tag and Package Manager dropdowns populate automatically with the most relevant options for your language. Set the port your app listens on. Toggle the three best-practice options to match your needs. The Dockerfile output updates instantly with every change. Click Copy to grab it for your clipboard, or Download to save a file named Dockerfile with no extension, ready to drop into your project root.

Why Instruction Order Is a Performance Decision

Docker rebuilds every instruction after the first one that has changed since the last build. If you copy all your source files before installing dependencies, a one-line change in any file forces a full reinstall on the next build. The correct pattern is to copy only the dependency manifest first, run the install, then copy the rest of the source. This way, dependency installation is only repeated when the manifest actually changes, keeping your CI pipeline fast even on large codebases.

Multi-Stage Builds: Where the Size Savings Come From

A Go binary compiled inside a golang:1.21-alpine image can be as small as 8 to 15 MB as a static binary. But the image containing the Go compiler and toolchain is around 250 MB. Multi-stage builds let you keep the compiler in the builder stage and copy only the finished binary into a FROM scratch or minimal Alpine image, producing a final image that is a fraction of the size. For Node.js, the same principle applies: the builder stage can install TypeScript and all devDependencies to compile your code, while the final stage installs only the production dependencies from package.json.

The Security Case for Non-Root Users

Running as root inside a container is the Docker equivalent of running your web server as root on a bare metal server: technically it works, but it maximizes the damage a successful exploit can do. The addgroup and adduser commands create a minimal system account with no login shell and no home directory. The USER directive then switches to that account before the CMD or ENTRYPOINT fires. Even if an attacker achieves remote code execution in your app, they are executing as an unprivileged user with no ability to modify system files or escalate privilege through standard paths.

Environment Variables That Change Runtime Behavior

Setting NODE_ENV=production tells Express, Next.js, and most other Node.js frameworks to disable development-only features like verbose error stacks and hot reloading, and to apply production-grade caching and optimizations. Setting PYTHONUNBUFFERED=1 forces Python to flush stdout and stderr immediately rather than buffering output, which is critical in containers because buffered output may never appear in your logs if the process is killed. For Go, CGO_ENABLED=0 tells the compiler to produce a statically linked binary with no dependency on C libraries, which is what allows the binary to run on a FROM scratch base image.

Frequently Asked Questions

Why should I use Alpine or Slim base images instead of the full official image? +
Alpine Linux images are typically 5 to 10 times smaller than their full Debian-based counterparts. A Node.js 20 image on Debian weighs around 350 MB; the 20-alpine variant comes in under 60 MB. Smaller images pull faster in CI pipelines, cost less to store in registries, and present a smaller attack surface because they ship with far fewer system packages. The main trade-off is that Alpine uses musl libc instead of glibc, which can occasionally cause compatibility issues with native Node.js modules or compiled Python extensions. Slim images (like python:3.12-slim) are a middle ground: still Debian-based and glibc-compatible, but with most non-essential packages stripped out.
How does a multi-stage build make my Docker image smaller? +
A multi-stage build uses more than one FROM instruction in a single Dockerfile. The first stage (the builder) installs compilers, build tools, and dev dependencies to produce the final artifact. The second stage starts from a fresh, minimal base image and copies only the compiled output, leaving all the build-time tooling behind. For a Go binary, the builder stage compiles a statically linked binary of 10 to 15 MB; the final stage can start FROM scratch and contain only that binary, dropping the total image from several hundred megabytes to under 20 MB.
Why is it dangerous to run containers as the root user? +
By default, processes inside a Docker container run as root (UID 0). If an attacker finds a vulnerability and achieves code execution, they are executing as root. If there is also a container-escape vulnerability in the Docker runtime or the kernel, a root process inside the container has a much greater chance of gaining root on the host machine. Creating a dedicated user with no login shell and switching to it with the USER directive follows the principle of least privilege. It limits what an attacker can do even if they gain control of the running process.
How does Docker layer caching speed up builds? +
Docker builds images layer by layer. Each layer is cached by a hash of its inputs. When you rebuild, Docker reuses cached layers up to the first instruction whose inputs have changed, then re-executes every instruction after that point. This is why copying only your dependency manifest first, running the install, and only then copying the rest of your source code is so important. Source code changes hit the cache for the slow install step and only re-run the fast COPY of your source files.
What is the difference between ENTRYPOINT and CMD in a Dockerfile? +
ENTRYPOINT sets the executable that always runs when the container starts. CMD provides the default arguments to that executable, and can be overridden at runtime. When both are present, ENTRYPOINT defines what runs and CMD defines the default arguments. For example, ENTRYPOINT ["node"] and CMD ["server.js"] runs node server.js by default, but docker run myimage app.js would run node app.js instead. Use ENTRYPOINT when you want a container to always run a specific executable, and CMD when you want an easily changeable default.