Skip to main content

Implementing Docker Security Best Practices

1. Running Containers as Non-Root

Option Values Notes
USER nonroot Set user to non-root user in Dockerfile
--user 1000:1000 Run container with specific UID/GID

Example: Set Non-Root User in Dockerfile

FROM ubuntu:20.04
RUN useradd --create-home --shell /bin/bash nonroot
USER nonroot
WORKDIR /home/nonroot
CMD ["echo", "Hello from non-root user"]

Example: Run Container with UID/GID

docker run --user 1000:1000 myapp
Note: Ensure the non-root user has necessary permissions for required operations inside the container.

2. Using Read-Only Filesystems

Option Values Notes
--read-only true Mounts the container's root filesystem as read-only
--tmpfs /tmp Mounts a temporary filesystem in memory for /tmp

Example: Run container with read-only root filesystem

docker run --read-only --tmpfs /tmp alpine sh -c 'ls /'
Note: The /tmp directory must be mounted as a tmpfs to allow temporary files to be written.
Warning: Containers with read-only filesystems may fail if they attempt to write to directories that are not explicitly mounted as tmpfs or volumes.

3. Dropping Kernel Capabilities

Capability Description Usage Example Notes
CAP_CHOWN Change file ownership docker run --cap-drop=CAP_CHOWN image Prevents container from changing file ownership
CAP_NET_ADMIN Modify network configuration docker run --cap-drop=CAP_NET_ADMIN image Blocks container from modifying network rules or interfaces
CAP_SYS_ADMIN Perform system administration tasks docker run --cap-drop=CAP_SYS_ADMIN image Prevents container from executing privileged operations like mounting filesystems
CAP_DAC_READ_SEARCH Bypass file read permission checks docker run --cap-drop=CAP_DAC_READ_SEARCH image Blocks access to files without read permissions
CAP_SETUID Change user ID docker run --cap-drop=CAP_SETUID image Prevents container from changing its effective user ID

Example: Dropping Capabilities in a Container

docker run --cap-drop=CAP_SYS_ADMIN \
           --cap-drop=CAP_NET_ADMIN \
           --cap-drop=CAP_CHOWN \
           myapp:latest
Note: Dropping capabilities reduces the attack surface by limiting what a compromised container can do. Always drop unnecessary capabilities.

4. Adding Required Capabilities

Capability Description Usage
CAP_NET_BIND_SERVICE Allows binding to privileged ports (< 1024) docker run --cap-add=NET_BIND_SERVICE myapp
CAP_SYS_ADMIN Allows privileged operations like mounting filesystems docker run --cap-add=SYS_ADMIN myapp
CAP_DAC_READ_SEARCH Bypasses file read permission checks docker run --cap-add=DAC_READ_SEARCH myapp
CAP_SETUID Allows changing user ID (e.g., to run as root) docker run --cap-add=SETUID myapp
Note: Only add capabilities that are strictly necessary. Prefer using --user or other security mechanisms over adding capabilities.

5. Setting Security Options

Option Description Default Notes
--user Sets the user for the container process root Use non-root user to reduce privilege escalation risk
--read-only Mounts the root filesystem as read-only false Helps prevent malicious writes to the filesystem
--no-new-privileges Prevents processes from gaining new privileges false Enforces the principle of least privilege
--security-opt Configures security options (e.g., seccomp, apparmor) none Can restrict system calls and capabilities
--cap-drop Drops specific capabilities from the container none Reduces attack surface by removing unnecessary permissions
--cap-add Adds specific capabilities to the container none Use with caution; only add what's necessary

Example: Running a container with security options

docker run --user 1000:1000 \
  --read-only \
  --no-new-privileges \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  --security-opt seccomp=unconfined \
  myapp:latest
Note: The --user option should point to a user ID that exists in the container's filesystem. It is recommended to create a dedicated non-root user inside your Docker image for better security.
Warning: Disabling --no-new-privileges can allow processes to escalate their privileges, which is a major security risk.

Security Option Summary

  • User isolation: Use --user to run containers as non-root users.
  • Read-only filesystem: Use --read-only to prevent unintended writes.
  • Capability control: Drop unnecessary capabilities with --cap-drop.
  • System call restriction: Use --security-opt seccomp=unconfined or custom profiles to restrict system calls.

6. Limiting Container Privileges

Principle Description Implementation
User Namespace Maps container root user to non-root user on host docker run --user 1000:1000 image-name
Read-Only Root Filesystem Prevents writes to root filesystem inside container docker run --read-only image-name
Drop Capabilities Removes unnecessary Linux capabilities from container docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE image-name
Disable Host Network Access Prevents container from accessing host network interfaces docker run --network=none image-name

Example: Running a Container with Limited Privileges

docker run --user 1000:1000 \
           --read-only \
           --cap-drop=ALL \
           --cap-add=NET_BIND_SERVICE \
           --network=none \
           --tmpfs /tmp \
           nginx
Note: The --user flag should map to a non-root user on the host for better security.
Warning: Dropping all capabilities (--cap-drop=ALL) might break applications that require specific system privileges.

7. Isolating Containers with User Namespaces

Feature Description Notes
user.namespace enabled Enables user namespace support in Docker
user.namespace.uid 0 UID mapping for the container's root user
user.namespace.gid 0 GID mapping for the container's root group

Example: Running a Container with User Namespace

docker run --userns=keep-id -u 1000:1000 ubuntu:20.04 whoami
Note: User namespaces isolate container processes from the host system by remapping UIDs/GIDs.
Warning: Requires Docker daemon configuration to enable user namespaces.

8. Configuring AppArmor Profiles

AppArmor is a Linux security module that allows system administrators to define security policies for applications. Docker containers can leverage AppArmor profiles to restrict container capabilities and enhance security.

Profile Description Usage
docker-container Default AppArmor profile for Docker containers Applied automatically by Docker daemon
unconfined Disables AppArmor for a container Use with caution; disables security restrictions

Example: Running a Container with AppArmor Profile

docker run --security-opt apparmor=docker-container nginx
Note: Ensure the AppArmor profile is loaded on the host system before using it with Docker containers.

9. Setting Up SELinux Labels

Label Type Description Usage Example
container_file_t Used for files inside containers that are not executable chcon -t container_file_t /path/to/container/file
container_exec_t Used for executable files inside containers chcon -t container_exec_t /path/to/container/executable
svirt_sandbox_file_t Used for sandboxed container files chcon -t svirt_sandbox_file_t /path/to/sandbox/file

Example: Applying SELinux Labels to Docker Volumes

chcon -Rt svirt_sandbox_file_t /var/lib/docker/volumes/myvolume/_data
Note: Ensure that the SELinux policy allows the container to access labeled files. Use semanage fcontext -l to view current file contexts.
Warning: Incorrect SELinux labels can prevent containers from accessing volumes or files, leading to permission errors.

10. Using Docker Content Trust

Docker Content Trust (DCT) helps ensure the integrity and origin of Docker images by signing and verifying image manifests and layers.

Feature Description Notes
Docker Content Trust Enables signing and verification of Docker images Requires Docker CLI v1.10+ and Docker Engine v1.10+
Notary Backend service for storing and managing image signatures Part of Docker Notary project
Signing Keys Private keys used to sign image manifests Keys are stored in $HOME/.docker/trust

Example: Enable Docker Content Trust

export DOCKER_CONTENT_TRUST=1
docker pull alpine:latest

Example: Inspect Signed Image

docker inspect --format='{{.Config.Image}}' alpine:latest
Note: DCT requires that all image layers and manifests are signed by a trusted key. Unsigned images will not be pulled.

11. Implementing Secrets Management

Secret Type Description Usage Example Best Practice
Docker Secrets Securely store and manage sensitive data such as passwords, tokens, or certificates. docker secret create db_password /path/to/password Use docker secret commands to manage secrets securely.
Environment Variables Pass sensitive data to containers via environment variables. docker run -e DB_PASSWORD=secret_value image_name Avoid hardcoding secrets in Dockerfiles or command-line arguments.
External Secret Stores Use external systems like HashiCorp Vault or AWS Secrets Manager. docker run -e VAULT_ADDR=https://vault.example.com image_name Integrate with secret management tools for centralized control.

Example: Using Docker Secrets in a Service

docker secret create api_key /path/to/api_key.txt
docker service create --secret api_key myapp:latest
Note: Secrets are not encrypted at rest in Docker Swarm mode. Ensure proper encryption and access controls.
Warning: Never store secrets in plain text files or logs. Use secure methods for handling sensitive data.

12. Using Official Base Images

Image Description Security Considerations
docker.io/library/ubuntu Ubuntu LTS base image with minimal packages Regular security updates; verify SHA256 checksums
docker.io/library/alpine Small, secure Linux distribution based on Alpine Minimal attack surface; uses musl libc and busybox
docker.io/library/centos Red Hat Enterprise Linux compatible base image Long-term support; requires subscription for full access
docker.io/library/node Official Node.js base image with npm and yarn Regular security patches; includes latest LTS version
docker.io/library/python Official Python base image with pip and venv Regular updates; supports multiple Python versions

Example: Pulling and Running Official Ubuntu Image

docker pull ubuntu:22.04
docker run -it ubuntu:22.04 /bin/bash

Example: Pulling and Running Official Node.js Image

docker pull node:20-alpine
docker run -it node:20-alpine /bin/sh
Note: Always use tagged versions (e.g., node:20-alpine) instead of latest to ensure reproducible builds.
Warning: Avoid using images from untrusted sources or third-party repositories without verifying their integrity and security.

Was this article helpful?