DEV Community

DoriDoro
DoriDoro

Posted on

Understanding the importance of binding the port in Dockerfile

For a Django project I created this dockerfile:

# Dockerfile 

FROM python:3.11-slim 

WORKDIR /app

ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1 

COPY ./requirements.txt . 

RUN pip install --upgrade pip && pip install -r requirements.txt 

COPY . . 

RUN python manage.py collectstatic 

EXPOSE 8000 

CMD ["gunicorn", "portfolio.wsgi:application", "--bind", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode

to build a Docker image on my local machine. The command to build the Docker image is:

docker build -t portfolio .
Enter fullscreen mode Exit fullscreen mode

portfolio is the name of the Docker image, and can be named like you want.

After building the Docker image on your local machine, you can run the Docker container with:

docker run -p 8000:8000 -e SECRET_KEY=secret -e ALLOWED_HOSTS='*' -e DEBUG=True portfolio
Enter fullscreen mode Exit fullscreen mode

-p setting the port
-e setting the environment variable

The server starts running like:

[2024-07-18 16:05:26 +0000] [1] [INFO] Starting gunicorn 22.0.0
[2024-07-18 16:05:26 +0000] [1] [INFO] Listening at: http://0.0.0.0:8000 (1)
[2024-07-18 16:05:26 +0000] [1] [INFO] Using worker: sync
[2024-07-18 16:05:26 +0000] [7] [INFO] Booting worker with pid: 7
Enter fullscreen mode Exit fullscreen mode

I was wondering why I have to bind the port in Dockerfile with EXPOSE and in the CMD, because when I start the server with command in terminal: gunicorn portfolio.wsgi:application I can also bind the port to the server, like: gunicorn portfolio.wsgi:application -b 0.0.0.0:8000.

But there is a big difference between binding the port in Dockerfile and just binding the port in the start-server-command. When I remove the EXPOSE and the CMD from the Dockerfile and use the command: gunicorn portfolio.wsgi:application -b 0.0.0.0:8000 to start the server, the provided port in terminal is http://127.0.0.1:8000 and the server does not find the Django project. It is not working.


Let's break down each aspect to understand the function of EXPOSE, CMD in your Dockerfile, and why specifying ports and bind addresses within the Dockerfile and docker run command are necessary or preferred.

EXPOSE in the Dockerfile

The EXPOSE instruction in a Dockerfile is essentially a way to document which ports the container's application will use. It does not actually publish the port or make it accessible from the host machine. Instead, it tells Docker which ports should be made accessible to linked services or other containers.

# Expose port 8000 to allow traffic to the application
EXPOSE 8000
Enter fullscreen mode Exit fullscreen mode

Why It's Useful:

  1. Documentation: It signals to anyone reading the Dockerfile which ports the container will use.
  2. Integration: Some Docker tools or orchestrators (like Kubernetes) can use this information to automatically configure network settings.

However, EXPOSE alone doesn't fulfill the function of making the port accessible from the host machine.

Binding to a Specific Address

The docker run command and the CMD instruction in your Dockerfile both serve important roles in making your application accessible and specifying how it should run.

# Define the command to run the application
CMD ["gunicorn", "portfolio.wsgi:application", "--bind", "0.0.0.0:8000"]
Enter fullscreen mode Exit fullscreen mode

The CMD instruction in the Dockerfile specifies the default command to run within the container. When using gunicorn, the --bind directive tells it to listen on a specific address and port.

  • 0.0.0.0:8000: This binds the application to all available network interfaces in the container, making it accessible from external sources (i.e., your host machine or other containers).
  • 127.0.0.1:8000: This binds the application to the loopback interface, making it only accessible within the container itself.

When you bind the server to 127.0.0.1, Docker's networking abstraction makes it so that the server is not directly accessible from the host machine or other containers. This is why you need to bind it to 0.0.0.0 to ensure the server is reachable via the container’s external interfaces.

Why Binding Ports in docker run is Still Necessary

The command:

docker run -p 8000:8000 -e SECRET_KEY=secret -e ALLOWED_HOSTS='*' -e DEBUG=True portfolio
Enter fullscreen mode Exit fullscreen mode
  • -p 8000:8000: This flag maps port 8000 on your host machine to port 8000 on the Docker container.

Even if you’ve specified an EXPOSE instruction in the Dockerfile, you still need to publish that port to make it accessible from your host machine. The -p flag in docker run makes this possible.

Running the Server Without EXPOSE and CMD

When you do not include the EXPOSE or CMD instructions in your Dockerfile, Docker neither knows which ports you intend to use for network traffic nor has a default command to run the application. Thus, you must specify these details manually in your docker run command for Docker to understand how to run and expose your application effectively.

Moreover, if your application is not bound to 0.0.0.0, mapping ports will not make it accessible via 0.0.0.0 because the server is only listening for requests on the localhost interface (127.0.0.1).

Conclusion

In summary:

  • EXPOSE 8000: Documents the port your application will use inside the container but does not publish it.
  • CMD with --bind 0.0.0.0:8000: Ensures the application listens on all network interfaces within the container.
  • -p 8000:8000 in docker run: Maps the container's port to the host machine.

Combining these configurations ensures your application is reachable from external sources, such as your host machine, and runs with the specified parameters or defaults.

Understanding these nuances can significantly enhance your control over containerized applications and their network configurations.

Top comments (0)