Dockerfile Generator

Generate a production-ready Dockerfile, docker-compose.yml and .dockerignore for Node, Python, Go, PHP or a static site. Multi-stage, non-root, healthcheck.

🌐 Español

🔒 Private by design: everything is generated locally in your browser and never uploaded to any server.

The exact Dockerfile the default settings produce

Nothing is hidden behind an abstraction here, so it is worth seeing the output before you decide whether it fits. Leave every option alone (Node.js, multi-stage on, non-root on, healthcheck off) and this is what lands in the box:

FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
RUN npm prune --production

FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package*.json ./
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

Sixteen instructions, and every one of them is doing something specific. The runtime stage never sees your dev dependencies, your compiler or your source tree, only the pruned module folder, the built output and the package manifest. It runs as the unprivileged node account that the official Alpine image already ships, so no user has to be created.

Layer cache order, and why the manifest is copied on its own

The most valuable detail in that file is the least visible one. package*.json is copied and installed before the rest of the source is copied in. Docker caches each instruction against the content of what it copied, so if you edit a source file and rebuild, the expensive install layer is reused untouched and only the last few instructions re-run. Copy everything at once instead and every single rebuild reinstalls the whole dependency tree.

The same ordering appears in every preset. Python copies requirements.txt and installs from it before the application arrives, into a virtualenv at /opt/venv when multi-stage is on. Go copies go.mod and go.sum and runs the module download first. PHP copies composer.json and composer.lock before anything else. It is the one habit that separates a container build that takes seconds from one that takes minutes.

Port 8080 is not a style preference

Linux will not let an unprivileged process bind any port below 1024. That rule is the reason a naive attempt to harden a web container fails: add a user switch on top of a stock Nginx or Apache image and the container dies at startup with a permission error on the socket, which reads like a file permissions problem and is not one.

So when the non-root option is on, the PHP and static-site presets do the full job rather than half of it. The generated file patches the server’s own configuration to listen on 8080, fixes ownership of the directories the server process needs to write to, and only then switches user. That is the same technique the official unprivileged Nginx image uses internally, applied by hand to a stock base image. Node, Python and Go are untouched by any of this, since their conventional ports already sit above the line.

Generate the Dockerfile, then the compose file, then the ignore file

  1. Choose your Language / framework from the five presets.
  2. Set Multi-stage build (separate build stage from a slim runtime stage), Run as a non-root user (security best practice) and Add a HEALTHCHECK directive to taste.
  3. Leave Which file to show on Dockerfile and click Dockerfile Generator, then Copy to clipboard.
  4. Click Generate more, switch Which file to show to docker-compose.yml, and generate again. Repeat once more for .dockerignore.

The healthcheck deserves a note. It probes every 30 seconds with a 3 second timeout and three retries, and the probe command is chosen for the base image rather than assumed. Alpine-based presets use the busybox wget that is already there; the Debian-based Python image has neither curl nor wget, so it uses the interpreter itself; the PHP image uses the php binary for the same reason. No preset installs an extra package just to answer a health probe.

Assumptions baked in that your project may not share

This is a template composer, not a project scanner, so it cannot see your repository. The Node preset assumes an npm run build script that emits into dist/ and an entry point at dist/server.js, or server.js at the root if you turn multi-stage off. Python assumes requirements.txt and app.py. Go assumes a module with a main package in the repository root. The static-site preset assumes a Node build producing dist/, so a site with no build step at all should have multi-stage switched off. Every one of these is a one-word edit once you have pasted the file in.

The .dockerignore is worth generating too, and not only for tidiness. It keeps .git, .env, the README and the Docker files themselves out of the build context, plus the per-language noise such as node_modules/ or __pycache__/. A smaller context uploads faster and, more importantly, stops a local node_modules from shadowing the one built inside the image. If you are setting up the repository at the same time, the .gitignore Generator covers the parallel job for Git.

A generator, not a linter

Everything above is composed from well-established idioms and assembled by plain JavaScript in your tab, with no Docker daemon anywhere in the loop. That means the output is syntactically standard and follows the patterns in the official image documentation, but it has never been built or run against a real engine. Treat it as a strong starting point: run docker build, start the image, and if you switched the healthcheck on, confirm the container reports healthy before any of this goes near production.

When the container needs routing rules or security headers of its own, the Nginx & .htaccess Config Generator produces the matching server configuration. To turn the generated compose file into JSON for a script, paste it into the YAML to JSON Converter. For a scheduled backup or rebuild alongside the service, the Crontab Expression Generator builds the expression, and the systemd Service Generator covers the case where you would rather run the thing natively. More of these live on the developer tools hub.

See it in action

Screenshot of the Dockerfile Generator tool with Language / framework set to Node.js (Express, Next.js API, etc.), Multi-stage build (separate build stage from a slim runtime stage) set to on
Dockerfile Generator mid-process: Language / framework set to Node.js (Express, Next.js API, etc.), Multi-stage build (separate build stage from a slim runtime stage) set to on.
Screenshot of the Dockerfile Generator result screen showing the generated output “FROM node:20-alpine AS build WORKDIR /app COPY p…”
The finished result: the generated output “FROM node:20-alpine AS build WORKDIR /app COPY p…”. The download link is a local blob URL — the file never leaves your device.

Frequently asked questions

Which port does each preset expose?

Node listens on 3000, Python on 8000 and Go on 8080, all of which are above 1024 and therefore unaffected by the non-root option. PHP and the static site normally use port 80, but switching on the non-root option moves both to 8080. Whichever value applies, the same number is used by EXPOSE, by the HEALTHCHECK URL and by the port mapping in the compose file, because all three read it from one shared function.

Why can I only see one of the three files at a time?

The options-plus-one-textarea layout this page uses can only ever display a single result, so a Which file to show selector picks between them instead. Your preset and toggles are resolved first and the requested file is produced from them, which means the compose file and the ignore file you get always match the Dockerfile you got. To see the next one, click Generate more to bring the options back, change the selector, and generate again.

There is no download button. How do I save the output?

Use Copy to clipboard and paste into a new file in your editor. That is a limit of this page layout rather than of the content, and it is not much of a hardship here since all three files have to be created by hand at the project root anyway, with the exact names Dockerfile, docker-compose.yml and .dockerignore, none of which your editor will suggest for you.

Why does the Go Dockerfile set CGO_ENABLED=0?

Without it, the Go toolchain can emit a binary dynamically linked against the build image's C library. Copy that binary into the minimal Alpine runtime stage and it dies at startup with a missing-library error that looks nothing like the real cause. Setting the flag forces a fully static binary, which runs on a stage that contains nothing but CA certificates. It is one of the most common real-world Go container mistakes.

Does turning off multi-stage actually cost me anything?

For most presets, yes. The single-stage Node build installs production dependencies directly and skips the prune step, the PHP one copies the Composer binary in and leaves it there, and the static site preset stops running a build entirely and just serves your working directory as-is. What you get back is a shorter file that is easier to read and reason about. The Go single-stage variant is the starkest trade, since it ships the entire Go toolchain image as your runtime.

Can I add my own environment variables, volumes or extra services?

Yes, everything produced here is ordinary text with no magic in it. The compose file in particular is a deliberate minimum, one service that builds the current directory, one port mapping, a restart policy, and for the Node preset a single NODE_ENV=production environment entry, so adding a database service, a named volume or an env_file line is exactly the same edit you would make to a hand-written one. Note there is no obsolete top-level version key to remove first.

Related tools