DEV Community

Cover image for Docker Container Security Hardening: Rootless, Seccomp, AppArmor & Read-Only Filesystems
DevsCampus
DevsCampus

Posted on

Docker Container Security Hardening: Rootless, Seccomp, AppArmor & Read-Only Filesystems

Docker Container Security Hardening: Rootless Mode, Seccomp, AppArmor & Read-Only Roots

Learn how to secure Docker containers using Rootless Mode, Linux Seccomp, AppArmor, and Read-Only Filesystems with practical examples and production-ready configurations.

Introduction

Docker has transformed how applications are developed, packaged, deployed, and operated. Containers provide portability, consistency, and isolation, making them an essential part of modern application infrastructure.

However, running an application inside a Docker container does not automatically make it secure.

A poorly configured container can expose the host system to serious security risks. Running containers as root, granting unnecessary Linux capabilities, allowing unrestricted system calls, and providing unrestricted filesystem access can create opportunities for privilege escalation and container breakout attacks.

Consider a production environment where multiple microservices run on the same Kubernetes worker node. If one container becomes compromised and has excessive privileges, an attacker may attempt to access sensitive resources, manipulate the host, or compromise other workloads.

This is why Docker container security hardening is an essential part of production infrastructure design.

In this article, we will explore four important Linux security mechanisms:

  1. Rootless Docker Mode
  2. Seccomp (Secure Computing Mode)
  3. AppArmor (Application Armor)
  4. Read-Only Root Filesystems

We will also combine these mechanisms into practical Docker configurations and discuss how to apply them in production environments.

If you are exploring containerization, Linux security, and DevOps engineering, visit DevCampus for more technical tutorials and practical engineering guides.


1. Understanding Docker Container Security

Before discussing individual security mechanisms, let's understand how Docker containers achieve isolation.

Docker relies on several Linux kernel features:

  • Namespaces: Isolate process IDs, networking, users, mounts, and other system resources.
  • Control Groups (cgroups): Control CPU, memory, and other resource consumption.
  • Linux Capabilities: Divide traditional root privileges into smaller permissions.
  • Seccomp: Restrict the system calls a process can execute.
  • AppArmor / SELinux: Enforce mandatory access control policies.
  • User Namespaces: Map container users to different user IDs on the host.

These mechanisms work together to provide container isolation.

However, isolation is not equivalent to complete security.

For example, consider this command:

docker run -it ubuntu bash
Enter fullscreen mode Exit fullscreen mode

The container may be isolated from other processes, but the application inside it could still run as root within its container namespace.

If additional privileges or dangerous host mounts are granted, the potential impact of a compromise increases.

A secure container should follow one fundamental principle:

Give every container only the permissions and resources it actually needs.

This is known as the Principle of Least Privilege.


2. Rootless Docker Mode

What Is Rootless Docker?

By default, the Docker daemon generally runs with root privileges.

This means the daemon has significant control over the host system, including the ability to create containers, manage filesystem mounts, and configure networking.

Rootless Docker changes this security model.

In Rootless Mode:

  • The Docker daemon runs as a non-root user.
  • Containers also run without requiring root privileges on the host.
  • User namespaces provide UID and GID mapping.
  • The impact of certain daemon and container vulnerabilities can be reduced.

The important distinction is that Rootless Mode protects both the Docker daemon and containers from relying on host-level root privileges.

How Rootless Mode Works

Consider the following example:

A normal Docker installation:

Host Operating System
        |
        | Root Privileges
        |
   Docker Daemon
        |
   Docker Container
        |
   Application
Enter fullscreen mode Exit fullscreen mode

Rootless Docker:

Host Operating System
        |
   Normal Linux User
        |
   Rootless Docker Daemon
        |
   User Namespace
        |
   Docker Container
        |
   Application
Enter fullscreen mode Exit fullscreen mode

The container's root user is mapped to an unprivileged user identity outside its user namespace.

This reduces the potential damage if an attacker gains control of a process inside the container.

Installing Rootless Docker

On a supported Ubuntu or Debian-based system, first install the required packages:

sudo apt update

sudo apt install -y uidmap dbus-user-session
Enter fullscreen mode Exit fullscreen mode

For Docker Engine installations using the official Docker packages, install the rootless extras if they are not already available:

sudo apt install -y docker-ce-rootless-extras
Enter fullscreen mode Exit fullscreen mode

Check whether the required UID mapping tools are available:

which newuidmap
which newgidmap
Enter fullscreen mode Exit fullscreen mode

Check subordinate UID and GID ranges:

grep "^$(whoami):" /etc/subuid

grep "^$(whoami):" /etc/subgid
Enter fullscreen mode Exit fullscreen mode

A user generally needs at least 65,536 subordinate UIDs and GIDs.

Install Rootless Docker:

dockerd-rootless-setuptool.sh install
Enter fullscreen mode Exit fullscreen mode

Start the user-level Docker service:

systemctl --user start docker
Enter fullscreen mode Exit fullscreen mode

Enable it at startup:

systemctl --user enable docker
Enter fullscreen mode Exit fullscreen mode

Set the Docker socket:

export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
Enter fullscreen mode Exit fullscreen mode

Verify the configuration:

docker info
Enter fullscreen mode Exit fullscreen mode

Look for:

Security Options:
  seccomp
  rootless
  cgroupns
Enter fullscreen mode Exit fullscreen mode

The presence of rootless indicates that the Docker client is connected to a rootless daemon.

Important: Rootless Docker has compatibility and operational limitations. For example, networking, storage drivers, privileged ports, and cgroup resource controls may behave differently depending on the kernel and system configuration.

Evaluate these requirements before adopting Rootless Mode in production.


3. Seccomp: Restricting Linux System Calls

What Is Seccomp?

Seccomp stands for Secure Computing Mode.

It is a Linux kernel security mechanism that restricts which system calls a process can execute.

Applications running inside containers interact with the Linux kernel through system calls.

Examples include:

  • open()
  • read()
  • write()
  • socket()
  • execve()
  • mount()
  • clone()

Most applications do not require access to every available Linux system call.

Allowing unnecessary system calls increases the kernel attack surface.

Seccomp helps reduce that attack surface by restricting system calls that a container can execute.

How Seccomp Works

Consider an application running inside a Docker container.

Without system-call restrictions:

Application
    |
    |
Linux System Calls
    |
    |
Linux Kernel
Enter fullscreen mode Exit fullscreen mode

With Seccomp:

Application
    |
    |
System Call Request
    |
    |
Seccomp Filter
    |
    |---- Allowed System Call
    |
    |---- Blocked System Call
    |
Linux Kernel
Enter fullscreen mode Exit fullscreen mode

If an application attempts to execute a restricted system call, the kernel applies the configured Seccomp action.

Depending on the policy, the operation may fail with a permission error or trigger another configured action.

Docker's Default Seccomp Profile

Docker provides a default Seccomp profile.

It blocks a collection of potentially dangerous system calls while allowing the majority of system calls required by normal applications.

You should generally keep this default profile enabled instead of disabling it.

To explicitly use the default profile:

docker run -d \
  --name secure-app \
  --security-opt seccomp=builtin \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

The default profile provides a practical security baseline for many workloads.

Creating a Custom Seccomp Profile

Some applications require additional restrictions.

You can create a custom JSON profile.

Example: seccomp-profile.json

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": [
    "SCMP_ARCH_X86_64"
  ],
  "syscalls": [
    {
      "names": [
        "read",
        "write",
        "openat",
        "close",
        "stat",
        "fstat",
        "mmap",
        "mprotect",
        "munmap",
        "brk",
        "rt_sigaction",
        "rt_sigprocmask",
        "ioctl",
        "access",
        "execve",
        "exit",
        "exit_group",
        "arch_prctl",
        "set_tid_address",
        "set_robust_list",
        "prlimit64",
        "getrandom",
        "futex"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This is an illustrative allowlist, not a production-ready universal profile. Real applications need additional system calls depending on their runtime, architecture, libraries, and workload.

Run the container using the custom profile:

docker run -d \
  --name secure-nginx \
  --security-opt seccomp=./seccomp-profile.json \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Seccomp Best Practices

  1. Never disable Seccomp without a strong technical justification.
  2. Start with Docker's default profile.
  3. Create workload-specific profiles only when required.
  4. Test custom profiles against application functionality.
  5. Monitor denied system calls.
  6. Avoid copying restrictive profiles between unrelated applications without testing.

For most production workloads, Docker's default Seccomp profile is a good starting point.


4. AppArmor: Mandatory Access Control for Containers

What Is AppArmor?

AppArmor stands for Application Armor.

It is a Linux Security Module (LSM) that provides mandatory access control.

While Seccomp restricts system calls, AppArmor controls what resources an application can access and how it can interact with them.

For example, an AppArmor policy can restrict an application from:

  • Reading sensitive host files.
  • Modifying protected system directories.
  • Accessing specific filesystem paths.
  • Performing certain privileged operations.
  • Interacting with resources outside its permitted boundaries.

AppArmor applies security policies to processes rather than relying only on traditional Linux file permissions.

How AppArmor Works

Imagine a web application running inside a container.

The application may need access to:

/app
/etc/app
/var/log/app
Enter fullscreen mode Exit fullscreen mode

However, it does not need access to:

/etc/shadow
/root
/proc/kcore
Enter fullscreen mode Exit fullscreen mode

An AppArmor policy can restrict access to resources that the application does not require.

Conceptually:

Docker Container
       |
       |
Application Process
       |
       |
AppArmor Security Policy
       |
       |---- Allowed File Access
       |
       |---- Denied File Access
       |
Linux Kernel
Enter fullscreen mode Exit fullscreen mode

Checking AppArmor Status

On Ubuntu, check whether AppArmor is enabled:

sudo aa-status
Enter fullscreen mode Exit fullscreen mode

Check whether the AppArmor service is running:

sudo systemctl status apparmor
Enter fullscreen mode Exit fullscreen mode

Docker normally applies its built-in docker-default AppArmor profile to containers when AppArmor is available and enabled.

You can explicitly specify it:

docker run -d \
  --name secure-web \
  --security-opt apparmor=docker-default \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Creating a Custom AppArmor Profile

For applications with specific security requirements, you can define custom AppArmor policies.

Create a profile:

sudo nano /etc/apparmor.d/docker-secure-app
Enter fullscreen mode Exit fullscreen mode

Example:

#include <tunables/global>

profile docker-secure-app flags=(attach_disconnected) {

  #include <abstractions/base>

  file,

  deny /etc/shadow r,
  deny /root/** rwklx,

  /app/** r,
  /var/log/app/** rw,

  deny mount,
  deny ptrace,

}
Enter fullscreen mode Exit fullscreen mode

This is a simplified illustrative policy. Production AppArmor profiles should be generated and validated against actual application behavior, including required executable access, libraries, networking, and writable directories.

Load the profile:

sudo apparmor_parser -r \
  /etc/apparmor.d/docker-secure-app
Enter fullscreen mode Exit fullscreen mode

Run a container with the custom profile:

docker run -d \
  --name secure-app \
  --security-opt apparmor=docker-secure-app \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

AppArmor Best Practices

  • Keep AppArmor enabled on supported Linux hosts.
  • Use the default Docker profile as a baseline.
  • Create custom policies for applications with stricter requirements.
  • Test policies before production deployment.
  • Review denied operations using system audit logs.
  • Avoid using unconfined unless there is a justified requirement.

Note: Rootless Docker has platform and feature limitations, including AppArmor support limitations in the documented Rootless implementation. Do not assume that every security mechanism is available in every Docker operating mode.


5. Read-Only Root Filesystem

What Is a Read-Only Root Filesystem?

A container's root filesystem contains its application binaries, libraries, configuration files, and other operating-system resources.

By default, a running container may be able to modify files inside this filesystem.

This creates a security risk.

For example, if an attacker compromises a web application, they may attempt to:

  • Modify application binaries.
  • Install malicious scripts.
  • Replace executable files.
  • Create persistence mechanisms.
  • Modify configuration files.

A read-only root filesystem reduces these possibilities.

Docker provides the --read-only option to mount the container's root filesystem as read-only.

Running a Container with a Read-Only Filesystem

Example:

docker run -d \
  --name secure-nginx \
  --read-only \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Now the container cannot modify its root filesystem.

However, there is one important consideration.

Many applications need writable directories for temporary files, logs, sockets, or runtime data.

A read-only filesystem may cause these applications to fail if writable paths are not configured.

Using tmpfs for Temporary Files

Docker supports temporary in-memory filesystems using --tmpfs.

For example:

docker run -d \
  --name secure-nginx \
  --read-only \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  --tmpfs /var/cache/nginx:rw,noexec,nosuid,size=64m \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Here:

  • --read-only protects the root filesystem.
  • /tmp provides temporary writable storage.
  • /var/cache/nginx provides writable cache storage.
  • noexec prevents execution of binaries from those mounts.
  • nosuid prevents set-user-ID and set-group-ID bits from taking effect.
  • size=64m limits the temporary filesystem size.

The writable paths must be selected according to the actual application's requirements.

Using Read-Only Volumes

Sometimes an application needs to read configuration files but should not modify them.

You can mount a volume as read-only:

docker run -d \
  --name secure-app \
  --mount type=bind,source=/opt/app/config,target=/app/config,readonly \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

This prevents the container from modifying the mounted configuration directory through that mount.

Remember that read-only root filesystems and read-only volume mounts solve different problems. Both may be needed.


6. Additional Docker Security Hardening Techniques

Rootless Mode, Seccomp, AppArmor, and read-only filesystems are important, but production security requires additional controls.

6.1 Run Containers as Non-Root Users

Avoid running application processes as root inside containers whenever possible.

Example Dockerfile:

FROM nginx:stable

USER 10001
Enter fullscreen mode Exit fullscreen mode

For a custom application image:

FROM ubuntu:24.04

RUN groupadd -g 10001 appgroup && \
    useradd -u 10001 -g appgroup -m appuser

WORKDIR /app

COPY --chown=appuser:appgroup . /app

USER appuser

CMD ["./application"]
Enter fullscreen mode Exit fullscreen mode

Ensure that the application has permission to access the directories and resources it requires.

6.2 Drop Unnecessary Linux Capabilities

Linux capabilities divide traditional root privileges into smaller permissions.

By default, Docker grants containers a limited set of capabilities.

However, applications may not need all of them.

You can drop all capabilities and add back only the required ones:

docker run -d \
  --name secure-app \
  --cap-drop ALL \
  --cap-add NET_BIND_SERVICE \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Here, the container receives only the capability to bind privileged network ports, subject to the relevant namespace and runtime configuration.

Do not add capabilities automatically. Determine which ones the application actually requires.

6.3 Disable Privilege Escalation

Use:

docker run -d \
  --name secure-app \
  --security-opt no-new-privileges:true \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

This prevents processes from gaining additional privileges through mechanisms such as setuid and setgid executables.

6.4 Limit CPU and Memory

A compromised application can also consume excessive host resources.

Use resource limits:

docker run -d \
  --name secure-app \
  --memory=512m \
  --cpus=1.0 \
  --pids-limit=100 \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

This helps protect the host against resource exhaustion.

6.5 Avoid Privileged Containers

Avoid:

docker run --privileged ubuntu
Enter fullscreen mode Exit fullscreen mode

Privileged containers receive extensive permissions and significantly weaken container isolation.

Use specific capabilities, device access, and security options instead of enabling privileged mode.

6.6 Scan Container Images

Container images may contain outdated packages, vulnerable dependencies, and unnecessary tools.

Scan images before deployment.

For example, using Trivy:

trivy image \
  --severity HIGH,CRITICAL \
  --ignore-unfixed \
  nginx:latest
Enter fullscreen mode Exit fullscreen mode

Integrate vulnerability scanning into CI/CD pipelines so that vulnerable images can be identified before reaching production.


7. Production-Ready Docker Security Configuration

Let's combine multiple security controls into one example.

The following command demonstrates a hardened container configuration:

docker run -d \
  --name production-app \
  --read-only \
  --user 10001:10001 \
  --cap-drop ALL \
  --security-opt no-new-privileges:true \
  --security-opt seccomp=builtin \
  --security-opt apparmor=docker-default \
  --memory=512m \
  --cpus=1.0 \
  --pids-limit=100 \
  --tmpfs /tmp:rw,noexec,nosuid,size=64m \
  -p 8080:8080 \
  my-application:1.0
Enter fullscreen mode Exit fullscreen mode

This example combines:

Security Control Purpose
--read-only Prevents modifications to the root filesystem
--user Runs the application as a non-root user
--cap-drop ALL Removes unnecessary Linux capabilities
no-new-privileges Prevents privilege escalation
seccomp=builtin Uses Docker's built-in Seccomp profile
apparmor=docker-default Applies Docker's default AppArmor profile
--memory Limits memory consumption
--cpus Limits CPU consumption
--pids-limit Limits process creation
--tmpfs Provides controlled temporary writable storage

Production consideration: This is a security-hardening template, not a universal command that will work unchanged for every application. The image must support the specified non-root UID, port, writable paths, and runtime behavior. AppArmor also requires a compatible host configuration.

For an additional security layer, evaluate Rootless Docker and user namespace isolation.


8. Docker Compose Security Configuration

In real-world environments, applications are frequently deployed using Docker Compose.

You can apply many of the same security controls through Compose.

Example compose.yaml:

services:
  application:
    image: my-application:1.0

    container_name: secure-application

    user: "10001:10001"

    read_only: true

    cap_drop:
      - ALL

    security_opt:
      - no-new-privileges:true
      - seccomp=builtin
      - apparmor=docker-default

    tmpfs:
      - /tmp:rw,noexec,nosuid,size=64m

    mem_limit: 512m

    cpus: 1.0

    pids_limit: 100

    ports:
      - "8080:8080"

    restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

Deploy:

docker compose up -d
Enter fullscreen mode Exit fullscreen mode

Inspect the running container:

docker inspect secure-application
Enter fullscreen mode Exit fullscreen mode

Check its security configuration and runtime settings.

Note that Compose resource-limit support and behavior can depend on the Compose implementation and deployment environment.


9. Docker Security Hardening in Kubernetes

Docker security principles also apply to Kubernetes workloads.

Kubernetes provides container security settings through the Pod and container securityContext.

For example:

apiVersion: v1
kind: Pod

metadata:
  name: secure-application

spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    runAsGroup: 10001
    seccompProfile:
      type: RuntimeDefault

  containers:
    - name: application

      image: my-application:1.0

      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true

        capabilities:
          drop:
            - ALL

      resources:
        limits:
          memory: "512Mi"
          cpu: "1"

        requests:
          memory: "128Mi"
          cpu: "100m"
Enter fullscreen mode Exit fullscreen mode

This configuration applies several important security controls:

  • Runs the application as a non-root user.
  • Uses the runtime's default Seccomp profile.
  • Prevents privilege escalation.
  • Makes the root filesystem read-only.
  • Drops unnecessary Linux capabilities.
  • Defines CPU and memory limits.

For Kubernetes environments, AppArmor can also be configured using supported security profiles and runtime integrations.

Modern Kubernetes deployments should also evaluate Pod Security Admission and the Restricted Pod Security Standard.

Important: Kubernetes does not automatically make a workload secure just because it runs inside a container. Security settings must be deliberately configured and validated.


10. Common Docker Security Mistakes

Even experienced developers sometimes introduce security weaknesses through convenience-based configurations.

Here are some mistakes to avoid.

Mistake Security Risk Recommended Approach
Running everything as root Increases privilege-escalation impact Use non-root users
Using --privileged Weakens container isolation Grant only required capabilities
Disabling Seccomp Expands kernel attack surface Keep the default profile
Using AppArmor unconfined Removes an additional security layer Use an appropriate enforced profile
Writable root filesystem Allows unwanted filesystem modifications Use --read-only
Mounting host directories unnecessarily Can expose sensitive host resources Use restricted mounts
No resource limits Can cause resource exhaustion Set CPU, memory, and PID limits
Using outdated images Exposes known vulnerabilities Scan and update images
Exposing Docker socket Can grant extensive host control Restrict access to the Docker API

One especially important warning:

Never mount /var/run/docker.sock into an application container unless there is a well-understood and carefully controlled requirement.

Access to the Docker socket can provide extensive control over the Docker daemon and, depending on the host configuration, the underlying system.


11. Docker Security Hardening Checklist

Before deploying a container to production, review the following checklist.

Container Identity

  • [ ] Run containers as non-root users.
  • [ ] Evaluate Rootless Docker where appropriate.
  • [ ] Avoid unnecessary root privileges.

Kernel Security

  • [ ] Keep Seccomp enabled.
  • [ ] Use the default Seccomp profile as a baseline.
  • [ ] Enable AppArmor or an appropriate alternative LSM.
  • [ ] Avoid unnecessary Linux capabilities.
  • [ ] Prevent privilege escalation.

Filesystem Security

  • [ ] Enable read-only root filesystems where possible.
  • [ ] Use tmpfs for temporary writable data.
  • [ ] Mount configuration volumes as read-only.
  • [ ] Avoid unnecessary host filesystem mounts.

Runtime Security

  • [ ] Set CPU and memory limits.
  • [ ] Set process limits.
  • [ ] Avoid privileged containers.
  • [ ] Restrict network exposure.
  • [ ] Monitor container activity.

Image Security

  • [ ] Use trusted base images.
  • [ ] Scan images for vulnerabilities.
  • [ ] Remove unnecessary packages.
  • [ ] Keep dependencies updated.
  • [ ] Use immutable image tags or digests in production.

Conclusion

Docker container security is not achieved through a single configuration or security mechanism.

It requires multiple layers of protection working together.

Rootless Mode reduces the dependency on host-level root privileges. Seccomp restricts access to unnecessary Linux system calls. AppArmor enforces application-level access policies. Read-only root filesystems prevent unwanted modifications to container files.

When combined with non-root users, restricted Linux capabilities, resource limits, and vulnerability scanning, these controls provide a stronger foundation for production container deployments.

For DevOps engineers, platform engineers, SREs, and backend developers, understanding these security mechanisms is essential when building reliable container-based infrastructure.

Remember: A container should have only the permissions, system calls, filesystem access, and resources required to perform its intended job.


Continue Learning with DevCampus

Want to understand more about Docker, Kubernetes, Linux security, and production infrastructure?

Explore DevCampus for practical engineering tutorials, architecture explanations, and technical learning resources.

Keep learning. Keep building. Build production-ready systems.

DevCampus — Learn Technology. Build Real Skills.
Docker & Container Engineering — Complete Docker learning roadmap covering container internals, security hardening, and hands-on labs.

Docker Container Engineering Career Path — Learn Docker architecture, BuildKit, container security, and Kubernetes migration.

Docker Security: Privileged Containers and Host Takeover — Understand container privilege escalation and Kubernetes security.

OCI Specifications and Container Architecture — Understand OCI, container runtimes, and image specifications.

Interactive Docker Sandboxes — Practise container security and Docker engineering concepts.

Top comments (0)