Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Academy Container Image Security
Intermediate · 20 min

Container Image Security

Learn how using latest tags, unverified base images, and layer secrets create security risks in container deployments.

1 Latest Tags, Unverified Images, and Layer Secrets

Container images have their own supply chain security risks beyond application-level vulnerabilities.

Using :latest tags:

FROM node:latest  # Which version? Unpredictable!
# Next week, node:latest might be a different major version
# Breaking changes, new vulnerabilities, or backdoored image

Unverified base images:

# FROM some-random-docker-hub-user/ubuntu-node:20
# Who published this? Is it maintained? Does it contain malware?

# Even official images can have CVEs — always scan!
docker scan myapp:latest
grype myapp:latest  # Checks for known CVEs in image layers

Secrets in image layers:

# DANGEROUS: secrets in RUN commands are stored in image layers
RUN pip install mypackage && rm /etc/secrets  # secrets still in layer!

FROM base AS builder
COPY secrets /etc/secrets  # Secret in builder layer — extractable!
RUN build-step

# Even in multi-stage, if you COPY from builder containing secrets...
docker history myapp:latest  # Shows all layer commands
docker save myapp:latest | tar xvf - | examine_layers

2 Pinned Digests, Minimal Base Images, and Multi-Stage Builds

Use pinned image digests, minimal base images, and multi-stage builds to create secure, reproducible containers.

Pin images by digest (immutable):

# By tag (mutable — can change!):
FROM node:20-alpine

# By digest (immutable — exact image, always):
FROM node:20-alpine@sha256:a7db2bea9b2e77c6d2ee46ad4bf78f56d7e88b18b07a28f5b2e8e6c618d6cfd4

# Get current digest:
docker pull node:20-alpine
docker inspect node:20-alpine --format='{{index .RepoDigests 0}}'

Multi-stage build — no secrets in final image:

FROM node:20-alpine AS builder
# Build step — may use build secrets
# --mount=type=secret makes secrets available without storing in layers
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
RUN npm run build

# Final image — no build tools, no secrets
FROM gcr.io/distroless/nodejs20-debian12
COPY --from=builder /app/dist /app/dist
COPY --from=builder /app/node_modules /app/node_modules
# Distroless: no shell, no package manager, minimal attack surface
USER nobody
CMD ["/app/dist/server.js"]

Knowledge Check

0/3 correct
Q1

Why is using FROM node:latest in a Dockerfile a security risk?

Q2

How can secrets accidentally stored in intermediate Docker layers be extracted?

Q3

What is the security advantage of using distroless base images?

Code Exercise

Pin Base Image Version

The Dockerfile uses :latest and copies sensitive build credentials into the image. Pin the base image to a specific version and use a multi-stage build to keep credentials out of the final image.

dockerfile