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"]
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
--userto run containers as non-root users. - Read-only filesystem: Use
--read-onlyto prevent unintended writes. - Capability control: Drop unnecessary capabilities with
--cap-drop. - System call restriction: Use
--security-opt seccomp=unconfinedor 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 |
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.