Building a Docker image from scratch can seem daunting at first, but a Dockerfile is really just a step-by-step recipe. Whether you're containerizing a FastAPI app or an Nginx server, you need to understand exactly what each instruction does.
Here is a concise breakdown of the core Dockerfile concepts and best practices.
1. The Base Image: Avoid :latest and Use slim
When choosing your base image with the FROM instruction, never use the :latest tag. It is a moving target that defeats the entire point of container reproducibility. Instead, pin a specific version.
Furthermore, prefer slim variants (like python:3.12-slim) over the default images. The standard python:3.12 image is a full Debian operating system, whereas the -slim version discards redundant system files and packages, drastically reducing your image size.
2. Setting Up the Workspace: WORKDIR and COPY
To get your code into the container, you typically see the combination of WORKDIR and COPY.
-
WORKDIR /app: This sets/appas the current directory inside the container's filesystem. Think of it as a persistentcdcommand for the rest of the image build. -
COPY . .: This copies everything from your host machine to the container. The first.represents your build context (the current directory on your host), and the second.represents the destination (the container's current working directory, which we just set to/app).
3. Ports: The Truth About EXPOSE
It is a common misconception that EXPOSE 8000 publishes a port to your host machine. It does not.
EXPOSE serves strictly as documentation for developers and automated tools, claiming that the container is listening on that port. To actually access the application from the outside, you still need to map the port at runtime using docker run -p 8000:8000.
4. Running the App: CMD vs. ENTRYPOINT
These two instructions dictate what runs when your container starts, but they behave differently:
-
CMD: Sets the default command and its arguments. If a user passes arguments todocker run, theCMDinstruction gets completely overridden. -
ENTRYPOINT: Sets the core executable. Any arguments passed atdocker runare appended to this executable rather than overriding it. This is ideal for containers designed to act as single-purpose CLI tools.
5. Always Use the "Exec" Form
When writing your CMD or ENTRYPOINT, always use the JSON array format (e.g., CMD ["uvicorn", "main:app"]), known as the exec form.
If you use the plain text shell form (e.g., CMD uvicorn main:app), Docker wraps your process in /bin/sh -c. This is dangerous because it breaks signal functionality. If your container acts as a single executable tool (like a web server or reverse proxy), the shell wrapper prevents system signals like SIGTERM from reaching your app, making graceful shutdowns impossible.
6. Inspecting Your Work
Once your image is built, you can audit how it was constructed using a single command:
docker history <image-name>
This command breaks down your built image layer by layer, showing you the exact size of each step and the specific Dockerfile instruction that created it. It is your best friend for debugging bloated images.
Top comments (0)