Remove Build Secrets from an Image

DockerBeginner
Practice Now

Introduction

Build secrets can leak into images when a Dockerfile copies too much of the build context. The common mistake is COPY . /app/: it is convenient, but it can bring tokens, credentials, local notes, and temporary files into the final image. Once a secret is inside an image layer, runtime controls cannot reliably erase the supply-chain mistake.

In this lab, you will build a deliberately leaky local image, prove that a staged build token is present inside it, then fix the build by narrowing the Dockerfile copy boundary and adding .dockerignore. Finally, you will deploy the clean image and verify that the runtime container no longer contains the build secret.

Inspect the Insecure Build Context

In this step, you will inspect the build context and the insecure Dockerfile. The build context is the directory sent to the image builder. If the Dockerfile copies the whole context, every file in that directory can become part of the final image.

Start in the lab workspace:

cd /home/labex/project/build-secret-image

Read the notes:

sed -n '1,120p' BUILD_SECRET_NOTES.md

List the files that currently exist in the build context:

find . -maxdepth 1 -type f -printf '%f\n' | sort | tee evidence/initial-context-files.txt

Review the insecure Dockerfile:

sed -n '1,120p' Dockerfile.insecure | tee evidence/insecure-dockerfile-review.txt

The risky line is COPY . /app/. It copies app.sh, but it also copies build-token.txt. In the next step, you will build this image and prove the leak from inside the image runtime.

Build the Leaky Image and Prove the Secret Is Present

In this step, you will build the insecure image inside the labex-v134 minikube profile. Building inside the profile makes the image visible to the Kubernetes node runtime.

Copy the insecure Dockerfile into the active Dockerfile name and build the leaky image:

cp Dockerfile.insecure Dockerfile
minikube -p labex-v134 image build -t labex/leaky-build:1.0 .

Run the image inside the minikube Docker runtime and check for the token file:

minikube -p labex-v134 ssh -- 'docker run --rm --entrypoint /bin/sh labex/leaky-build:1.0 -c "ls -l /app && cat /app/build-token.txt"' | tee evidence/leaky-image-contents.txt

You should see build-token=labex-temporary-secret. That is the leak. The image runs, but it contains a file that should have stayed outside the final runtime image.

Build a Clean Image with a Narrow Copy Boundary

In this step, you will fix the image build. Two changes matter: .dockerignore keeps the token out of the build context sent to the builder, and the Dockerfile copies only app.sh instead of copying the whole directory.

Create .dockerignore:

cat > .dockerignore <<'EOF'
build-token.txt
evidence/
*.txt
EOF

Create the clean Dockerfile:

cat > Dockerfile <<'EOF'
FROM busybox:1.36

WORKDIR /app
COPY app.sh /app/app.sh
RUN chmod 0555 /app/app.sh

USER 65534:65534
ENTRYPOINT ["/app/app.sh"]
EOF

Build the clean image:

minikube -p labex-v134 image build -t labex/clean-build:1.0 .

Inspect the clean image from inside the minikube Docker runtime:

minikube -p labex-v134 ssh -- 'docker run --rm --entrypoint /bin/sh labex/clean-build:1.0 -c "ls -l /app; if [ -f /app/build-token.txt ]; then echo BUILD_SECRET_PRESENT; else echo NO_BUILD_SECRET; fi; id -u"' | tee evidence/clean-image-contents.txt

The output should show NO_BUILD_SECRET and UID 65534.

Deploy the Clean Image

In this step, you will deploy the clean image and verify the running container reports that no build secret is present. This keeps the final proof close to Kubernetes, where the image will actually run.

Create and apply the Pod manifest:

cat > clean-build-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: clean-build-app
  namespace: cks-prep-build-secret
  labels:
    app: clean-build-app
spec:
  restartPolicy: Never
  containers:
    - name: app
      image: labex/clean-build:1.0
      imagePullPolicy: Never
      securityContext:
        runAsNonRoot: true
        runAsUser: 65534
        runAsGroup: 65534
        allowPrivilegeEscalation: false
EOF
kubectl apply -f clean-build-pod.yaml

Wait for the Pod and read its logs:

kubectl -n cks-prep-build-secret wait --for=condition=Ready pod/clean-build-app --timeout=120s
kubectl -n cks-prep-build-secret logs clean-build-app | tee evidence/clean-pod-logs.txt

The logs should include:

clean build image ready
NO_BUILD_SECRET

Summary

You inspected an insecure image build context, built a leaky image, proved the build token was present, then fixed the build with .dockerignore and a narrow Dockerfile COPY instruction. You built and deployed a clean image that runs as a non-root user and reports NO_BUILD_SECRET. This completes the supply-chain stage by connecting image build hygiene to Kubernetes runtime verification.