Secure Dockerfiles with cnspec
Scan Dockerfiles for security misconfigurations with cnspec.
Catch insecure container image patterns at the source by scanning Dockerfiles before an image is ever built. cnspec evaluates Dockerfiles against the Mondoo Dockerfile Security and Mondoo Dockerfile Best Practices policies, flagging issues like running as root, missing health checks, mutable base image tags, and use of ADD instead of COPY.
This page is part of scanning your supply chain with cnspec. If you're new to cnspec, start with the Quickstart to install cnspec and run your first scan.
Prerequisites
To scan Dockerfiles with cnspec, you must have:
- cnspec installed on your workstation
- Dockerfiles on your local system to scan
Scan a Dockerfile
Scan a single Dockerfile, substituting the path and name of the file for FILEPATH:
cnspec scan docker file FILEPATHFind nested Dockerfiles within a directory, substituting the directory path for PATH:
cnspec scan docker file PATHScan with the Mondoo Dockerfile policies
Mondoo maintains out-of-the-box Dockerfile Security and Dockerfile Best Practices policies that flag running as root, missing health checks, mutable base image tags, use of ADD instead of COPY, and more.
Mondoo Platform users: Enable the policies in your space. In the Mondoo App, go to Findings > Policies, search for "Dockerfile", and add the policies. All future scans of your Dockerfiles automatically evaluate against them. To learn more, read Manage policies in Mondoo Platform.
Open source users: Pass a policy bundle URL directly to cnspec. Use the security policy:
cnspec scan docker file FILEPATH \
--policy-bundle https://raw.githubusercontent.com/mondoohq/cnspec/refs/heads/main/content/mondoo-dockerfile-security.mql.yamlOr the best-practices policy:
cnspec scan docker file FILEPATH \
--policy-bundle https://raw.githubusercontent.com/mondoohq/cnspec/refs/heads/main/content/mondoo-dockerfile-best-practices.mql.yamlExplore Dockerfiles
Run cnspec shell docker file FILEPATH to open the interactive shell and explore your Dockerfiles.
List stages in a multi-stage build
cnspec> docker.file.stages
docker.file.stages: [
0: from.name="build"
1: from.name=""
]Retrieve base images
Get the base image for a specific stage:
cnspec> docker.file.stages[0].from
docker.file.stages[0].from: name="build" image="ubuntu" tag="22.04"
in Dockerfile:1-1
1: FROM ubuntu:22.04 AS buildGet all FROM instructions across stages:
cnspec> docker.file.stages { from }
docker.file.stages: [
0: {
from: name="build" image="node" tag="18-alpine"
in Dockerfile:1-1
1: FROM node:18-alpine AS build
}
1: {
from: name="" image="nginx" tag="alpine"
in Dockerfile:14-14
14: FROM nginx:alpine
}
]Retrieve RUN instructions
cnspec> docker.file.stages[0].run
docker.file.stages[0].run: [
0: script="apt-get update"
in Dockerfile:5-5
5: RUN apt-get update
1: script="apt-get install -y curl"
in Dockerfile:6-6
6: RUN apt-get install -y curl
]Retrieve USER configuration
cnspec> docker.file.stages[0].user
docker.file.stages[0].user: user="appuser"
in Dockerfile:10-10
10: USER appuserRetrieve exposed ports
cnspec> docker.file.stages[0].expose
docker.file.stages[0].expose: [
0: port=8080 protocol="tcp"
in Dockerfile:9-9
9: EXPOSE 8080
]Retrieve environment variables
cnspec> docker.file.stages[0].env
docker.file.stages[0].env: [
0: name="NODE_ENV" value="production"
in Dockerfile:3-3
3: ENV NODE_ENV=production
1: name="APP_PORT" value="8080"
in Dockerfile:4-4
4: ENV APP_PORT=8080
]Retrieve COPY instructions
cnspec> docker.file.stages[0].copy { src dst chown chmod }
docker.file.stages[0].copy: [
0: {
src: [
0: "package.json"
]
dst: "/app/"
chown: "appuser:appuser"
chmod: ""
}
]Retrieve ADD instructions
cnspec> docker.file.stages[0].add { src dst }
docker.file.stages[0].add: [
0: {
src: [
0: "https://example.com/config.tar.gz"
]
dst: "/opt/"
}
]Retrieve build arguments
cnspec> docker.file.stages[0].arg { name default }
docker.file.stages[0].arg: [
0: {
name: "NODE_VERSION"
default: "18"
}
]Retrieve image labels
cnspec> docker.file.stages[0].labels
docker.file.stages[0].labels: {
maintainer: "team@example.com"
version: "1.0"
}Retrieve entrypoint and CMD
cnspec> docker.file.stages[0].entrypoint
docker.file.stages[0].entrypoint: script="node server.js"
in Dockerfile:11-11
11: ENTRYPOINT node server.jscnspec> docker.file.stages[0].cmd
docker.file.stages[0].cmd: script="npm start"
in Dockerfile:12-12
12: CMD npm startList all instructions
cnspec> docker.file.instructions
docker.file.instructions: [
0: {
original: "FROM node:18-alpine AS build"
}
1: {
original: "RUN apt-get update"
}
2: {
original: "COPY --chown=appuser:appuser package.json /app/"
}
...
]Example security checks
Ensure the Dockerfile does not use the root user
cnspec> docker.file.finalStage.runsAsRoot == false
[ok] value: false[ok] means the check passed. The value after it is the value of runsAsRoot that cnspec compared, so value: false confirms that the final stage does not run as root.
Ensure a HEALTHCHECK instruction is defined
cnspec> docker.file.finalStage.hasHealthcheck
[ok] value: trueVerify no stage uses the latest tag
cnspec> docker.file.stages.all(from.tag != "latest")
[ok] value: trueVerify COPY is used instead of ADD
cnspec> docker.file.stages.all(add == empty)
[ok] value: trueVerify no RUN instructions use sudo
cnspec> docker.file.stages.all(run.none(script.contains("sudo")))
[ok] value: trueScan options
| Option | Description |
|---|---|
--asset-name | Override the asset name |
--annotation | Add an annotation to the asset (key=value) |
--incognito | Run in incognito mode (do not report results to Mondoo Platform) |
-o, --output | Set the output format (compact, csv, full, hdf, json, junit, ocsf-json, ocsf-parquet, report, sarif, summary, yaml) |
-f, --policy-bundle | Path to a policy file (local path, s3:// URI, or http(s):// URL) |
--policy | Specify policies to execute (requires --policy-bundle) |
--risk-threshold | Exit with status 1 if any risk meets or exceeds this value (0-100) |
To gate a CI/CD pipeline, combine --risk-threshold with a report file, for example --risk-threshold 90 --output junit --output-target cnspec-junit.xml. 90 fails the job only on critical risks, 70 also fails on high risks, and 40 also fails on medium risks. To scan every infrastructure as code entry point in a repository in one step, use cnspec scan iac. For complete pipeline examples, read Integrate Mondoo with CI/CD Platforms.
Learn more
-
To scan Docker images, read Secure Docker Images with cnspec.
-
To scan running containers, read Secure Docker Containers with cnspec.
-
To learn more about how the MQL query language works, read Write Effective MQL.
-
Explore the Dockerfile resource reference.