Dockerfile Generator

Pick the stack your service runs on, set the port, flip a few switches. The Dockerfile, its .dockerignore, plus the build commands update as you type, with a note for every choice made on your behalf.

Stack
Runtime
Container
Environment
One KEY=value per line. Names holding SECRET, TOKEN, PASSWORD or KEY stay out of the image and move to the run command.
Output
Dockerfile

Getting a Dockerfile to build once on your laptop takes five lines. Getting one to rebuild in seconds on the fiftieth commit, ship without a compiler inside, run as a normal user, then pass an image scan takes more thought. The generator above writes the second kind for six common stacks. The sections below explain the reasoning behind its lines, so you know which ones to keep when you edit the file by hand.

What each stack gets from the generator

Every template splits the work into two stages. The first holds compilers, dev dependencies, the package cache. The second receives only the output, so none of the build tooling reaches production.

StackBuild stageRuntime stageReason for the runtime pick
Node.jsnode:22-alpine, all dependenciesnode:22-alpine, production dependencies onlySame base in both stages, so native modules built in stage one load in stage two.
Static sitenode:22-alpinenginx:stable-alpineNode never reaches the final image. Only built HTML, CSS, JS, plus nginx.
Pythonpython:3.13-slim with a venvpython:3.13-slimSlim over Alpine on purpose. See the next section.
Gogolang:1.26-alpineDistroless static, scratch, or AlpineA binary built with cgo off needs no libc. The runtime holds the binary plus CA certificates.
Javaeclipse-temurin:21-jdkeclipse-temurin:21-jreThe JRE drops javac plus the JDK tools.
PHPcomposer:2php:8.4-fpm-alpineComposer stays behind. FPM needs a web server container in front.

Why Python stays on slim

Most wheels on PyPI target glibc, the C library Debian uses. Alpine ships musl. A package without a musllinux wheel compiles from source during pip install, which means installing gcc plus headers first. The build slows from seconds to minutes, the image grows, then a C extension fails on some musl difference nobody on your team has seen before. Slim skips the whole problem at roughly the same final size.

Why Java skips the openjdk image

Docker deprecated the official openjdk images in 2022, so they stopped receiving security fixes. Older tutorials still show openjdk:17-jdk-slim, which is what the previous version of this page generated too. Eclipse Temurin builds from the same OpenJDK source with regular patch releases. Amazon Corretto plus Azul Zulu work as drop-in alternatives if your company standardizes on one.

Layer order decides rebuild time

Docker caches each instruction. When the inputs of one instruction change, every instruction after it reruns. A COPY treats the content of the files it copies as its input, so a single edited source file invalidates COPY . . plus every line below.

Reinstalls packages on every code edit
FROM node:22-alpine
WORKDIR /app
COPY . .
RUN npm ci
RUN npm run build
Reinstalls only when the lockfile changes
FROM node:22-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

Every template above follows the second pattern: manifest first, install, source last. A few other things bust the cache without anyone noticing:

Non-root without breaking file writes

Containers run as root unless the Dockerfile says otherwise. With user namespaces off, which is the default, a container escape bug then hands the attacker root on the host. Each stack switches users a different way:

Node.js
The official image ships a node user with uid 1000. One USER node line, no useradd.
Python, Java
Debian plus Ubuntu bases carry no app user, so the file runs useradd with uid 10001. A uid above 10000 avoids matching a real account on the host when you bind-mount folders.
Go
The distroless :nonroot tag runs as uid 65532. Scratch has no /etc/passwd, so the file sets the numeric 65532:65532 directly.
Static site
Stock nginx starts as root. The nginxinc/nginx-unprivileged image runs as uid 101 on port 8080, which is why the port changes when you tick non-root.
PHP
www-data already exists. Only storage plus bootstrap/cache get chowned to it.

The generated files never chown the whole app folder. Code owned by root but run by a normal user means a compromised process cannot rewrite its own source. Grant write access only to the folders your app writes to at run time.

Secrets stay out of ENV or ARG

Every ENV value lands in the image config. Anyone who pulls the image reads the value with docker inspect. ARG values used in a RUN step show up in docker history. Type SESSION_SECRET=abc into the variables box above to see the guard: the generator drops the line from the Dockerfile, flags the key, then adds -e SESSION_SECRET to the run command so the value comes from your shell.

Private registries during the build leak the same way. An .npmrc holding an auth token, copied in then deleted two steps later, still sits in the earlier layer. A BuildKit secret mount exposes the file to one RUN step without writing it to any layer:

RUN --mount=type=secret,id=npmrc,target=/root/.npmrc npm ci
docker build --secret id=npmrc,src=$HOME/.npmrc -t your-service .

A health check needs a program to run

HEALTHCHECK runs a command inside the container. If the base image lacks the command, the check fails on every attempt, so Docker reports a working app as unhealthy. Here is what each runtime image offers:

Runtime imageAvailable clientCheck the generator writes
Node, nginx, AlpineBusyBox wgetwget -qO- against the health path
Python slimPython, no curl or wgetpython -c with urllib
Temurin JREVaries by releaseInstalls curl first, then curl -fsS
Distroless, scratchNothingNo check, plus a note on adding a flag to the binary
PHP-FPMFastCGI onlyNo check. Probe the nginx container instead

Kubernetes ignores HEALTHCHECK entirely. Pods use the liveness plus readiness probes from the manifest. The Dockerfile check still matters under Docker Compose, Swarm, or anything reading docker ps.

Where the generator stops

Dockerfile questions from code review

Cache misses, Alpine versus slim, the PHP port, BuildKit, plus the missing Go health check.

Why does my build reinstall every package after a one-line code change?

The source copy runs before the install step, or a folder such as .git changes on every commit. Copy the lockfile first, install, then copy the rest, which is the order every template here uses. Check the .dockerignore tab as well, since a missing .git entry busts the cache on each commit.

Should a Python image use Alpine or slim?

Slim. Most PyPI wheels target glibc while Alpine ships musl, so any package without a musllinux wheel compiles from source. The build then needs gcc plus headers, takes minutes instead of seconds, with a final image often larger than the slim one.

Why does the PHP image listen on 9000 instead of 80?

PHP-FPM speaks FastCGI, not HTTP, so a browser pointed at port 9000 gets nothing useful. Run nginx or Caddy in a second container, forward PHP requests to port 9000, then serve static files from the public folder. The Nginx Config Generator writes the server block for this setup.

Will the legacy Docker builder accept the generated file?

Not with cache mounts or the nginx config block switched on. Both need BuildKit, the default builder since Docker Engine 23 plus every recent Docker Desktop release. On an older engine, set DOCKER_BUILDKIT=1 before building, or untick the cache mount option.

Why is there no HEALTHCHECK line in my Go image?

Distroless plus scratch images hold no shell, wget, or curl, so Docker has nothing to run. Add a flag to your binary which calls your own health endpoint then exits 0 or 1, then point HEALTHCHECK at the binary in exec form. Or pick the Alpine runtime, which keeps wget.

Does anything I type here leave the browser?

No. The Dockerfile, the .dockerignore, plus the commands are all built by JavaScript in this tab. Environment values, including any secret you paste by mistake, never reach a server. A reload clears every field.