Dockerfiles built on a laptop are not necessarily suitable for production. They may leave the secret in layers, float dependency from the tag, run the program with root, or keep the build files in the final image. The criterion of a good Dockerfile is not just the volume: it must be reproducible, testable, up-to-date, and fit the actual behavior of the program.
Quick answer:Select a valid, copy-based base image; manage dependencies with a lockfile and multi-stage build; just bring the necessary artifact to runtime; don't enter the secret image; have the non-root user, exec-form command, and shutdown path clear. Create the final image in CI, publish it with digest, and run the same digest in staging and production.
Before you write the first order, clear the execution contract.
Dockerfile cannot resolve the architectural ambiguity. List the start command, port, writeable data path, network dependencies, config, stop signal, and standby condition. If the program needs manual editing inside the container to go up, first specify and repeat it in the build or migration process.Dockerize the app.This opens up the need.
Base image: specified version, authenticated source, patch cycle
Use the right runtime image and target architecture.latestPin digest makes the build result more traceable, but if you never update it, you lose the base patches. So alongside pin, schedule rebuild, scan and promotion. A smaller image is not more secure on its own; set the required packages, CA certificate, timezone, and native library by real test.
Control the build context
COPY . .I don't know..dockerignoreIt can also input repository, cache, backup, environment file, and personal data into context. Even if they are not copied at the final stage, they may have reached the builder. Write the ignore list according to the project and repeat the build in CI with a clean context..gitLogs, test exits and confidential files should not normally be image inputs; record the exception carefully.
Layers and cache order.
Otherwise, each small change will re-execut the installation of all dependencies. Details are different for Node, Python, or Go, but the principle is the same: copy the manifest and lockfile earlier than the source, then install or build.
Multi-stage: separate the construction tool from runtime
The compiler, package manager, test runner, and development header are usually not required in the application execution image. The builder phase creates the artifact, and the runtime phase executes only the output and time dependency commands.COPY --from=buildIt's clear across the border.The multi-stage build guideThe difference between stage, target and risk of copying the wrong dependencies explains it.
A small template, not a ready-made version for each language.
FROM golang:1.26 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -trimpath -o /out/app ./cmd/server
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=build /out/app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
This is just for the Go with the path../cmd/serverAnd a consistent outletCGO_ENABLED=0It is not necessary to blindly copy the underlying images for the native dependency program. The basic images must be copied in the pin project and updated. A program that requires a specific config file, template or certificate must transmit them explicitly and with appropriate permission. Runtime phase with actual testing; successful build does not mean running healthy.
What happens to Secret in Build?
Private registry code, token package and SSH keyARGI'm not going to.ENVOr...COPYDo not place. Deleting the file in the next layer does not necessarily delete its runtime from history. If build requires credential, use the appropriate BuildKit secret or SSH mount and make sure the command does not copy it to the log or output artifact.
non-root USER and file permissions
If the program doesn't require special privileges,USERRun. Own the copied file, pre-determin the cache path and volume; running non-root without the required write permission only transfers the error to run time. A fixed UID/GID can be useful on shared volume, but must be aligned with the host and orchestrator policies.
ENTRYPOINT, CMD and receiving signal
It's like a row of trees.ENTRYPOINT ["/app"]The shell-form may not send the signal to the main process unless the entrypoint forwardes it correctly. When stopped, the program must receive SIGTERM and collect the connection and job with limited time. If a startup script is required, its output must be withdrawn.execDeliver to the main process and not hide the error.
Health Check is useful, not decorative.
Healthcheck should be cheap, fast, and health-representative. Requesting a local endpoint of the program is better than just seeing a PID, but checks that depend on all external services can lead to a temporary disruption of a dependency. Consider the program and the runtime separately: not all healthcheck needs to be in a Dockerfile; some are defined in a Compose or orchestrator. External or readiness does not take into account when rollout.
EXPOSE is not a security or port release.
EXPOSEThe contract is an image port lock and does not open the port on the Internet alone. Publishing the port is done in the run settings. The database and cache often require only the internal network. If the application is not running with the nonroot user on the lower port, instead of adding an unreasonable privilege, check the higher internal port and mapping proxy.
System files and persistent data
Image should be replaceable. Keep the important upload, database and state in the defined storage and design the Backup and Restore separately.VOLUMEDockerfile may be useful for a public image, but the exact location and lifecycle of the data in deployment should be clear. Cache and temporary files also take up real space; if you read-only the filesystem, specify the limited writeable path.
Final image testing, not just the build phase.
- Run the CI dependency and test.
- Create the final image from a clean context.
- Scan the image for known secrecy and vulnerability.
- Runtime and health test with the same user.
- Check stop with signal and restart.
- Test with a similar configuration and volume to staging.
- Record the digest and transfer it to production without any independent rebuild.
The scan does not give a definitive result; the advisories change and the program code must be checked. Good-node testing is not enough: check for lack of dependency, incorrect permission, and delayed program preparation.
Mistakes that are common.
- Using a floating tag without digest recording
- Putting it down.
.envIn the build context - Install the build tool in the image runtime
- Running root just to make it easier to write files.
- Re-create the image for each environment instead of the same artifact promotion.
- Assuming the plan was safe after
docker build - Ignoring the database migration and copy return
When the team needs help.
If a large image is unrepeatable, build or release, the dependency, artifact, and runtime path should be checked before extreme shrinkage.The app's decryption serviceIt can design Dockerfile, CI, secret and execution behavior together and test in staging.
Common Questions
Does every Dockerfile have to be multi-stage?
No technical requirement, but when you have build tools or temporary files, stage separation is usually valuable.
Is it enough to remove package manager from runtime?
No, updates to the base, process permissions, secret, network and program code are also important.
Should we belatestUse it?
For production, it is best to deploy a specific artifact with a digest or a recorded copy to see what is being executed.
Why isn't the program running after build?
Runtime files, native libraries, write path permissions, config or CPU architecture may differ from build phase; test the final image separately.