DEV Community

Cover image for 7 Easy-to-Follow Best Practices for Writing Dockerfile
Takashi Yoneuchi
Takashi Yoneuchi

Posted on

7 Easy-to-Follow Best Practices for Writing Dockerfile

Dockerfile allows you to create container images in such a way as if you were writing shell scripts. This simplicity is excellent indeed, but it does NOT mean that you don't need to learn good practices for writing Dockerfile. Just a few practices will make your container images more optimized and secure.

This article shows a list of 7 best practices for writing Dockerfile along with some Shisho rules to detect the issues. You may not know Shisho, but it's okay because Shisho is extremely easy to use; all you need to do for checking your Dockerfile is just by running the following command:

curl https://raw.githubusercontent.com/security-aware-repo-examples/dockerfile-best-practice/master/rules/docker.shisho.yaml > docker.shisho.yaml && \
    docker run -i -v $(pwd):/workspace ghcr.io/flatt-security/shisho-cli:latest check ./docker.shisho.yaml .
Enter fullscreen mode Exit fullscreen mode

If you're a user of GitHub Actions, you can copy this example workflow and the rule file in security-aware-repo-examples/dockerfile-best-practice to your repository. The workflow checks your repository with the rule, and it reports them to GitHub Code Scanning. The issues will appear at https://github.com/<your org>/<your repository>/security/code-scanning.

Image description

Let's get started!


♻️ Three Practices for Maintainability

1. Avoid to use latest tag for immutability

A latest tag is used to create a docker image whose base is the latest version of another image. The use of latest tag, however, might cause confusion and inconsistent behaviour among built images. It is better to pin the version of your base images if possible.

The following Shisho rule will detect the use of latest tag:

version: '1'
rules:
  - id: 'use-fixed-tag-sfor-immutablity'
    language: dockerfile
    message: |
      The use of `latest` tag might cause confusion and inconsistent behavior in automated builds. It is better to pin the version of your base images.
    patterns: 
      - pattern: FROM :[IMAGE]
      - pattern: FROM :[IMAGE] as :[ALIAS]
      - pattern: FROM :[IMAGE]:latest
      - pattern: FROM :[IMAGE]:latest as :[ALIAS]
      - pattern: FROM :[IMAGE]@:[HASH]
      - pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
      - pattern: FROM :[IMAGE]:latest@:[HASH]
      - pattern: FROM :[IMAGE]:latest@:[HASH] as :[ALIAS]
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

2. Use an idiomatic way to run apt-get install

You may often run apt-get inside your Dockerfile as follows:

RUN apt-get update
RUN apt-get install -y nginx <... and some more packages>
Enter fullscreen mode Exit fullscreen mode

However, the example above has two issues:

  • When the cache image for the first RUN is available, the second apt-get might install old packages due to not running apt-get update.
  • Some layers for this image have cache files for apt-get, resulting in larger image size.

A simple way to address these issues is running apt-get update and apt-get install in a single RUN instruction. The following examples show an idiomatic way to run them once while removing /var/lib/apt/lists/* (i.e. apt caches) to reduce the image size more:

RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

The following Shisho rule will enforce the use of this idiom:

version: '1'
rules:
  - id: 'remove-cache-of-apt-get'
    language: dockerfile
    message: |
      It is better to remove cache files of `apt-get` to keep your image slim.
    pattern: |
      RUN apt-get install :[X]
    rewrite: |
        RUN apt-get update && \
            apt-get install :[X] && \
            rm -rf /var/lib/apt/lists/*
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

3. Use --no-install-recommends flag of apt-get

It is even better to avoid to install any unnecessary tools by --no-install-recommends of apt-get command. The following Shisho rule may help the use of this flag:

version: "1"
rules:
  - id: "use-no-install-recommends-flag-apt-get"
    language: dockerfile
    message: |
      You can avoid to install any unnecessary tools by `--no-install-recommends` on `apt-get`.
    patterns:
      - pattern: |
          RUN apt-get install :[...X]
      - pattern: |
          RUN :[...Y] apt-get install :[...X]
    constraints:
      - target: X
        should: not-match
        regex-pattern: ".*--no-install-recommends.*"
    rewrite_options:
      - |
        RUN :[Y] apt-get install --no-install-recommends :[X]
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)


🛡️ Four Practices for Security

While container technology brings many advantages, such as portability or isolation, it also creates new threats from a security perspective. This section describes common docker security issues and explains how to avoid them.

4. Avoid to store secrets in environment variables

Hardcoded secrets in your Dockerfile will be stored in resulting images. You should avoid to embed the secrets. You can inject environment variables at run-time instead.

version: '1'
rules:
  - id: 'avoid-to-store-secrets-in-env'
    language: dockerfile
    message: |
      Hardcoded secrets in your Dockerfile will be stored in resulting images. Please consider to stop embedding the secrets. 
    pattern: |
      ENV :[...] :[KEY]=:[VALUE] :[...]
    constraints:
      - target: KEY
        should: match-any-of
        regex-patterns:
          - "[sS][eE][cC][rR][eE][tT]"
          - "[tT][oO][kK][eE][nN]"
          # ... add as you like ...
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

5. Use trusted base images

A docker image consists of multiple layers, and some of them are usually derived from the base image. Here exists a risk of supply chain attacks! It is better to use official images to reduce the risk as much as possible.

version: '1'
rules:
  - id: 'use-docker-official-images'
    language: dockerfile
    message: |
      It is better to use official images to reduce the risk of supply chain attacks.
    patterns: 
      - pattern: FROM :[IMAGE]
      - pattern: FROM :[IMAGE] as :[ALIAS]
      - pattern: FROM :[IMAGE]::[TAG]
      - pattern: FROM :[IMAGE]::[TAG] as :[ALIAS]
      - pattern: FROM :[IMAGE]@:[HASH]
      - pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
      - pattern: FROM :[IMAGE]::[TAG]@:[HASH]
      - pattern: FROM :[IMAGE]::[TAG]@:[HASH] as :[ALIAS]
    constraints:
      - target: IMAGE
        should: match
        regex-pattern: "/"
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

If you already have a list of trusted base images, some slight changes will let you use them:

version: '1'
rules:
  - id: 'use-trusted-base-images'
    language: dockerfile
    message: |
      It is better to use trusted base images to reduce the risk of supply chain attacks.
    patterns: 
      - pattern: FROM :[IMAGE]
      - pattern: FROM :[IMAGE] as :[ALIAS]
      - pattern: FROM :[IMAGE]::[TAG]
      - pattern: FROM :[IMAGE]::[TAG] as :[ALIAS]
      - pattern: FROM :[IMAGE]@:[HASH]
      - pattern: FROM :[IMAGE]@:[HASH] as :[ALIAS]
      - pattern: FROM :[IMAGE]::[TAG]@:[HASH]
      - pattern: FROM :[IMAGE]::[TAG]@:[HASH] as :[ALIAS]
    constraints:
      - target: IMAGE
        should: not-match-any-of
        regex-patterns:
            - "^[^/]+$"
            - "^image-name/you-trust$"
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

6. Avoid to run curl <...> | sh

Many shell scripts and dockerfiles use curl <...> | sh as an idiomatic way to run an external script inside computers/containers, but this idiom has a risk of remote code execution by attackers through MITM attacks or compromising the distributed script itself (see the report of recent codecov incident). It is better to check the integrity of what you download before running it as a shell script.

version: "1"
rules:
  - id: "check-integrity-of-downloaded-shell-script"
    title: Check the integrity of downloaded shell scripts
    language: dockerfile
    message: |
      It is better to check the integrity of what you download before running it as a shell script.
    pattern: |
      RUN :[CMD]
    constraints:
      - target: CMD
        should: match-any-of
        regex-patterns:
          - curl[^|^>]*[|>]
          - wget[^|^>]*[|>]
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)

7. Use COPY instead of ADD

ADD instruction allows us to fetch resources over the network and extract an archive, but it may cause security issues such as Meet-in-the-Middle (MITM) attacks or Zip Slip vulnerabilities. It is better to use COPY instead of ADD if possible.

The following Shisho rule detects the use of ADD instructions:

version: '1'
rules:
  - id: 'use-copy-instead-of-add'
    language: dockerfile
    message: |
      ADD instruction allows us to fetch resources over network and extract an archive, but it may cause security issues such as Meet-in-the-Middle (MITM) attacks or Zip Slip vulnerabilities.
    pattern: ADD :[FROM] :[TO]
    rewrite: COPY :[FROM] :[TO]
Enter fullscreen mode Exit fullscreen mode

(You can see the working example of this rule at Shisho Playground)


Conclusion

In this article, I presented some best practices for writing Dockerfile and demonstrated how to check your Dockerfile is following the practices continuously by Shisho.

You can refer security-aware-repo-examples/dockerfile-best-practice to see the working example.

GitHub logo security-aware-repo-examples / dockerfile-best-practice

Shisho rules that enforce Dockerfile best practices

If you have an interest in Shisho, I would be appreciated if you starred the following repository and gave me feedback at discussions!

GitHub logo flatt-security / shisho

Lightweight static analyzer for several programming languages

shisho

shisho

GitHub Release GitHub Marketplace License Documentation Test Playground

Shisho is a lightweight static analyzer for developers.

Please see the usage documentation for further information.

demo

Try at Playground

You can try Shisho at our playground.

Try with Docker

You can try shisho in your machine as follows:

echo "func test(v []string) int { return len(v) + 1; }" | docker run -i ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go
Enter fullscreen mode Exit fullscreen mode
echo "func test(v []string) int { return len(v) + 1; }" > file.go
docker run -i -v $(PWD):/workspace ghcr.io/flatt-security/shisho-cli:latest find "len(:[...])" --lang=go /workspace/file.go
Enter fullscreen mode Exit fullscreen mode

Install with pre-built binaries

When you'd like to run shisho outside docker containers, please follow the instructions below:

Linux / macOS

Run the following command(s):

# Linux
wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-unknown-linux-gnu.zip -O shisho.zip
unzip shisho.zip
chmod +x ./shisho
mv ./shisho /usr/local/bin/shisho
# macOS
wget https://github.com/flatt-security/shisho/releases/latest/download/build-x86_64-apple-darwin.zip -O shisho.zip
unzip shisho.zip
chmod +x ./shisho
mv ./shisho /usr/local/bin/shisho
Enter fullscreen mode Exit fullscreen mode

Then you'll see a…


NOTE: I'm working on Shisho Cloud, a web service providing this kind of best practice checks for infrastructure-as-code and automated patch generation for detected issues. It's in a beta stage now and all features are available for free. Please try it!

Discussion (0)