Skip to content

Secure Dockerfiles

Harden Base Images

Component Description
Use minimal images
  • Use distroless
  • Use multistage builds
  • Use trusted base images
Use non-root user
  • Create a low-privilege user:
    • Build time: RUN groupadd -r user && useradd -r -g user user USER user
    • Runtime: docker run -u <username> -it <container_name> /bin/bash
  • Don't bind to a specific UID
  • Make executables owned by root and not writable
    • Avoid: COPY --chown=app:app app-files/ /app
Reduce attack surface
  • Remove users
  • Remove packages
  • Remove setuid/setgid permissions
    • Prohibit suid/sgid: docker run --cap-drop SETUID --cap-drop SETGID ...
    • Disable setuid rights: RUN find / -perm +6000 -type f -exec chmod a-s {} \; || true
Use COPY Use COPY instead of ADD in the Dockerfile
  • COPY: just copies the files from the local host machine to the container file system
  • ADD: potentially could retrieve files from remote URLs and perform operations such as unpacking
Restrict RUN Commands
  • Restrict and validate usage of RUN (which could be used to install additional packages)
Build context and dockerignore
  • Create a .dockerignore file to explicitly exclude files and directories

Process

Component Description
Update your images frequently
Linting Tools like hadolint can detect bad practices in your Dockerfile, and even expose issues inside the shell commands executed by the RUN instruction
Container Scanning See Container Scanning

Sample Dockerfiles

FROM alpine:3.3 (1)

ENV VERSION 1.11.2
ENV SHA256 8c2e0c35e3cda11706f54b2d46c2521a6e9026a7b13c7d4b8ae1f3a706fc55e1 (2)

WORKDIR /usr/bin

RUN apk update && \
        apk upgrade && \ (3)
        apk --update add coreutils wget ca-certificates && \
        wget https://get.docker.com/builds/Linux/x86_64/docker-$VERSION.tgz && \
        wget https://get.docker.com/builds/Linux/x86_64/docker-$VERSION.tgz.sha256 && \
        sha256sum -c docker-$VERSION.tgz.sha256 && \ (4)
        echo "$SHA256 docker-$VERSION.tgz" | sha256sum -c - && \ (4)
        tar -xzvf docker-$VERSION.tgz -C /tmp && \
        mv /tmp/docker/docker . && \
        chmod u+x docker* && \
        rm -rf /tmp/docker* && \ (5)
        apk del wget ca-certificates && \ (5)
        rm -rf /var/cache/apk/* docker-$VERSION.tgz docker-$VERSION.tgz.sha256 (5)

COPY ./docker-garby.sh /docker-garby.sh (6)

RUN useradd -d /home/<username> -m -s /bin/bash <username> (7)
USER <username> (7)

ENTRYPOINT ["/bin/sh", "/docker-garby.sh"]
  1. Do we trust the remote repository? Is there any reason we’re not using a homebuilt base image?
  2. Hash to verify downloaded file
  3. Keep the container up-to-date
  4. Verify downloaded files
  5. Remove unused applications and unnecessary directories
  6. COPY local files, ADD remote files
  7. Create an unprivileged USER if possible
  8. GnuPG sign the commit, git -s -S -m '...'
FROM scratch (1)
ADD ./wheezy-1603172157.txz / (2)
ENV SHA 00c3cc1b8968d3b5acf2ac9fc1e36f2aa30dfd4ff44a35d8d3bd1948914d722d (3)

ONBUILD RUN apt-get update && apt-get -y upgrade (4)
  1. Use scratch
  2. Add a compressed, minimal, base
  3. Hash for the above base
  4. Force containers based on this image to keep up-to-date
  5. GnuPG sign the commit, git -s -S -m '...'