Building a Secure Docker Image
๐ก๏ธ Building a Secure Docker Image โ Step-by-Step Lab
When it comes to container security, your Docker image is the foundation. The smaller and more focused the image, the smaller the attack surface. In this hands-on lab, weโll build a secure Docker image from a Django application using multi-stage builds and Distroless base images.
๐ง Step 1: Clone the Source Code
Letโs start by cloning the sample Django project that we'll use for this exercise.
git clone https://gitlab.practical-devsecops.training/pdso/django.nv.git
cd django.nv
You should now be inside the django.nv project folder containing all the source files.
๐ณ Step 2: Build a Basic Docker Image (Alpine-based)
Weโll first build a simple image using python:3-alpine3.15, a lightweight base image.
docker build -t django.nv:1.0 .
After the build completes, check your images:
docker images
๐ฆ Expected Output
REPOSITORY TAG IMAGE ID CREATED SIZE
django.nv 1.0 071343727408 Few seconds ago 106MB
โ Small, neat image! But can we do better? Letโs take a look at the Dockerfile.
cat Dockerfile
๐ Dockerfile (Alpine version)
FROM python:3-alpine3.15
WORKDIR /app
COPY . .
RUN apk add --no-cache gawk sed bash grep bc coreutils \
&& pip install -r requirements.txt \
&& chmod +x run.sh
EXPOSE 8000
CMD ["/app/run.sh"]
Good for starters. But not secure enough for production. Letโs level up.
๐ Step 3: Enhance Security with Distroless and Multi-Stage Build
Distroless images by Google contain only your application and its runtime dependenciesโno package manager, shell, or even ls. This dramatically reduces attack surface.
๐๏ธ Multi-Stage Dockerfile (Distroless)
Letโs rewrite our Dockerfile using a build image and a final distroless image.
cat >Dockerfile<<EOF
FROM debian:11-slim AS build
COPY requirements.txt .
RUN apt-get update \
&& apt-get install --no-install-suggests --no-install-recommends --yes python3-venv gcc libpython3-dev \
&& python3 -m venv /venv \
&& /venv/bin/pip install --upgrade pip
FROM build AS build-venv
COPY requirements.txt /requirements.txt
RUN /venv/bin/pip install -r /requirements.txt
FROM gcr.io/distroless/python3-debian11
WORKDIR /app
COPY --from=build-venv /venv /venv
COPY . .
RUN /venv/bin/python3 manage.py migrate \
&& /venv/bin/python3 manage.py loaddata fixtures/*
EXPOSE 8000
ENTRYPOINT ["/venv/bin/python3", "manage.py", "runserver", "0.0.0.0:8000"]
EOF
Now build the new image:
docker build -t django.nv:1.1 .
Check the image list again:
docker images
๐ฆ Expected Output
REPOSITORY TAG IMAGE ID CREATED SIZE
django.nv 1.1 dedad500d9de Just now 104MB
gcr.io/distroless/python3-debian11 latest aa70b0553cf1 N/A 54.5MB
๐ Almost same size, but significantly more secure!
โถ๏ธ Step 4: Run the Secure Container
Now weโll run the new container:
docker run -d --name django -p 8000:8000 django.nv:1.1
docker ps
Visit: http://localhost:8000 to check if the app is running.
๐ Step 5: Test for Restricted Access
Letโs see what happens when we try to get shell access into the container.
docker exec -it django bash
๐ซ Youโll get an error like:
exec: "bash": executable file not found in $PATH
Try with sh:
docker exec -it django sh
โ If youโre lucky, you might get shell access. Try running a few commands:
hostname
ls -l
๐ซ Most commands will fail โ no shell utilities available!
This is by design. It prevents attackers from using familiar tools to exploit the container.
๐ก Final Thoughts
Distroless images:
โ Minimize vulnerabilities
โ Reduce attack surface
โ Are ideal for production
But:
โ They are hard to debug
โ ๏ธ You might need additional tooling like sidecars or enhanced logging for production observability