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"]
to build a Docker image on my local machine. The command to build the Docker image is:
docker build -t portfolio .
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
-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
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
Why It's Useful:
- Documentation: It signals to anyone reading the Dockerfile which ports the container will use.
- 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"]
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
-
-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
indocker 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)