DEV Community

aykhlf yassir
aykhlf yassir

Posted on

Dockerfile Fundamentals

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 /app as the current directory inside the container's filesystem. Think of it as a persistent cd command 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 to docker run, the CMD instruction gets completely overridden.
  • ENTRYPOINT: Sets the core executable. Any arguments passed at docker run are 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>
Enter fullscreen mode Exit fullscreen mode

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)