Advanced Dockerfile Instructions
In this final step, we'll explore some additional Dockerfile instructions and best practices that can help make your Docker images more secure, maintainable, and easier to use. We'll also focus on troubleshooting and verifying each step of the process.
-
In WebIDE, open the Dockerfile
again.
-
Replace the content with the following:
## Build stage
FROM python:3.9-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
## Final stage
FROM python:3.9-slim
## Create a non-root user
RUN useradd -m appuser
## Install curl for healthcheck
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
WORKDIR /app
## Dynamically determine Python version and site-packages path
RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && \
SITE_PACKAGES_PATH="/home/appuser/.local/lib/python${PYTHON_VERSION}/site-packages" && \
mkdir -p "${SITE_PACKAGES_PATH}" && \
chown -R appuser:appuser /home/appuser/.local
## Copy site-packages and binaries using the variable
COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}"
COPY --from=builder /root/.local/bin /home/appuser/.local/bin
COPY app.py .
ENV PATH=/home/appuser/.local/bin:$PATH
ENV ENVIRONMENT=production
## Set the user to run the application
USER appuser
## Use ENTRYPOINT with CMD
ENTRYPOINT ["python"]
CMD ["app.py"]
EXPOSE 5000
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:5000/ || exit 1
ARG BUILD_VERSION
LABEL maintainer="Your Name <[email protected]>"
LABEL version="${BUILD_VERSION:-1.0}"
LABEL description="Flask app demo with advanced Dockerfile techniques"
Let's break down the new concepts introduced in this Dockerfile:
RUN useradd -m appuser
: This creates a new user named appuser
in the container. Running applications as a non-root user is a security best practice, as it limits the potential damage if the application is compromised. The -m
flag creates a home directory for the user.
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*
: This installs the curl package which is needed for our HEALTHCHECK instruction to work. We also clean up the apt cache to reduce the image size.
RUN PYTHON_VERSION=$(python -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")') && ...
: This set of commands dynamically determines the Python version within the container and creates the correct site-packages
directory for the appuser
. It also sets the correct permissions for the user's local directory.
COPY --from=builder /root/.local/lib/python3.9/site-packages "${SITE_PACKAGES_PATH}"
: This instruction copies the installed Python packages from the builder
stage to the dynamically determined site-packages
path within the final image, ensuring packages are placed in the correct location for the appuser
to use.
COPY --from=builder /root/.local/bin /home/appuser/.local/bin
: This copies executable scripts installed by pip
(like Flask's command-line interface, if any) from the builder
stage to the appuser
's local bin
directory.
ENTRYPOINT ["python"]
with CMD ["app.py"]
: When used together, ENTRYPOINT
defines the main executable for the container (in this case, python
), and CMD
provides the default arguments to that executable (app.py
). This pattern allows for flexibility: users can run the container and execute app.py
by default, or they can override the CMD
to run other Python scripts or commands.
HEALTHCHECK
: This instruction configures a health check for the container. Docker will periodically execute the specified command (curl -f http://localhost:5000/
) to determine if the container is healthy. The --interval=30s
and --timeout=3s
flags set the check interval and timeout respectively. If the curl
command fails (returns a non-zero exit code), the container is considered unhealthy.
ARG BUILD_VERSION
: This defines a build argument named BUILD_VERSION
. Build arguments allow you to pass values into the Docker image at build time.
LABEL version="${BUILD_VERSION:-1.0}"
: This sets a label named version
on the Docker image. It uses the BUILD_VERSION
build argument. If BUILD_VERSION
is provided during the build, its value will be used; otherwise, it defaults to 1.0
(using the :-
default value syntax).
- Now, let's build this new image, specifying a build version:
docker build -t advanced-flask-app-v2 --build-arg BUILD_VERSION=2.0 .
The --build-arg BUILD_VERSION=2.0
flag allows us to pass the value 2.0
for the BUILD_VERSION
build argument during the image build process. This value will be used to set the version
label in the Docker image.
- Once the build is complete, let's verify that the image was created successfully:
docker images | grep advanced-flask-app-v2
You should see the new image advanced-flask-app-v2
listed in the output of the docker images
command, along with its tag, image ID, creation date, and size.
- Now, let's run a container with the new image:
docker run -d -p 5002:5000 --name advanced-container-v2 advanced-flask-app-v2
This command runs a container in detached mode (-d
), maps port 5002 on your host to port 5000 in the container (-p 5002:5000
), names the container advanced-container-v2
(--name advanced-container-v2
), and uses the advanced-flask-app-v2
image to create the container.
- Let's verify that the container is running:
docker ps | grep advanced-container-v2
If the container is running successfully, you should see it listed in the output of the docker ps
command. If you don't see the container listed, it might have exited. Let's check for any stopped containers:
docker ps -a | grep advanced-container-v2
If you see the container listed in the output of docker ps -a
but it's not running (status is not "Up"), we can check its logs for errors:
docker logs advanced-container-v2
This command will display the logs of the advanced-container-v2
container, which can help diagnose any startup issues or runtime errors in your Flask application.
- Assuming the container is running, after giving it a moment to start up, we can check its health status:
docker inspect --format='{{.State.Health.Status}}' advanced-container-v2
After a short delay (to allow the health check to run at least once), you should see "healthy" as the output. If you see "unhealthy" initially, wait for another 30 seconds (the health check interval) and run the command again. If it remains "unhealthy", check the container logs using docker logs advanced-container-v2
for potential issues with your Flask application. If there are no obvious issues, you can ignore the "unhealthy" status.
- We can also verify that our build version label was correctly applied:
docker inspect -f '{{.Config.Labels.version}}' advanced-flask-app-v2
This command retrieves the value of the version
label from the advanced-flask-app-v2
image and displays it. You should see "2.0" as the output, which confirms that the BUILD_VERSION
build argument was correctly used to set the label.
- Finally, let's test our application by sending a request to it:
curl http://localhost:5002
You should see the message "Hello from production environment!" in the output. This indicates that your Flask application is running correctly inside the Docker container and is accessible on port 5002 of your host.
These advanced techniques allow you to create more secure, configurable, and production-ready Docker images. The non-root user improves security, the HEALTHCHECK
helps with container orchestration and monitoring, and build arguments allow for more flexible and versioned image building.