Join our Newsletter — 33% off our NHI Course
Home› Guides› Docker and Docker Compose Security Guide: Secure the…
Guide Cloud Security

Docker and Docker Compose Security Guide: Secure the Daemon, Socket, Secrets and Compose Files

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 28 October 2026 15 min read
On this page

On a typical Docker host, anyone who can reach the Docker daemon, by socket, by TCP or by running a Compose file, effectively has root on that host, and most real Docker incidents start there rather than with an exotic container escape. Docker Compose makes this easy to miss: a single compose.yaml can mount the host filesystem, publish ports to the internet, add capabilities and pass secrets as environment variables, and Compose applies all of it as written. This guide covers the Docker-specific and Compose-specific controls: protecting the daemon and its socket, the docker group, rootless and user namespace modes, safe Compose defaults, Compose and build secrets, port publishing and firewalls, and what to monitor. It is for developers, platform engineers and security teams running Docker Engine on servers, build agents and developer machines. For container security across the full lifecycle, see the Container Security Guide.

Key takeaways

  • The daemon is the boundary. Docker's own documentation says the docker group grants root-level privileges and that only trusted users should control the daemon; treat socket access as privileged access.
  • Never mount /var/run/docker.sock into a container you would not give host root. OWASP notes that a read-only mount does not make the API read-only.
  • A Compose file is code you run. Docker's Compose trust model treats every Compose file as trusted input, including nested include and extends references; review the resolved output of docker compose config before up.
  • Use secrets, not environment variables. Compose secrets mount as files under /run/secrets/, build secrets use --mount=type=secret, and build arguments and environment variables persist in the final image.
  • Published ports are public by default. ports: "8080:80" listens on all host addresses and Docker's NAT rules bypass ufw; bind to 127.0.0.1 unless the service must be reachable.

Why the Docker daemon is the real attack surface

The Docker Engine documentation splits Docker security into four areas: kernel namespaces and cgroups, the attack surface of the daemon, the container configuration profile, and kernel hardening features. The second is where most organisations get hurt. The daemon (dockerd) runs as root unless rootless mode is used, and it will do what any authorised client asks: start a container with the host's / directory mounted, in the host network and PID namespaces, with all capabilities. Docker's documentation is explicit that this lets a container alter the host filesystem without restriction, and that only trusted users should be allowed to control the daemon.

Access pathWhat it givesControl
Membership of the docker groupRoot-level privileges on the host, as Docker's post-install guide warnsTreat as privileged access; review membership; prefer rootless mode on shared hosts
/var/run/docker.sock mounted into a containerFull control of the daemon from inside that containerDo not mount it; if a tool truly needs the API, use an authorisation plugin or a scoped proxy and isolate the tool
TCP listener (-H tcp://..., ports 2375 and 2376)Remote root on the host for anyone who can connectDo not expose; if needed, TLS client certificates on 2376, restricted to a management network, or SSH (DOCKER_HOST=ssh://user@host)
CI job or agent with Docker accessHost root for anyone who can change the pipelineDedicated, ephemeral build hosts; rootless or daemonless builders; no shared long-lived agents
Running an untrusted Compose fileWhatever the file requests: bind mounts, privileged mode, host networkingReview docker compose config output; pin remote references to digests

Remote access deserves particular care. Docker's remote access page warns that opening the daemon to the network can let remote non-root users gain root on the host, and the default ports are 2376 with TLS and 2375 without. The engine security page states that exposing the daemon API over HTTP without TLS is not permitted and causes the daemon to fail early on startup in current releases, but older engines still in service accept it. Unauthenticated Docker APIs on the internet are routinely found and abused for cryptomining and credential theft, for example by the Carbonato botnet, which planted an agent on exposed Docker hosts to hunt AI API keys.

Rootless mode, user namespaces and daemon options

There are three ways to reduce what a compromise of the daemon or a container means for the host. They are not equivalent.

OptionHow it worksChoose whenLimits
Rootful daemon, non-root containersDaemon runs as root; images set USER and containers drop capabilitiesDefault for most servers; easiest compatibilityDaemon compromise or socket access is still host root
userns-remapDaemon runs as root, but container root is mapped to an unprivileged range from /etc/subuid and /etc/subgidShared hosts where container root must not equal host rootDaemon still root; some volume and host-namespace features need per-container exceptions
Rootless modeDaemon and containers run inside a user namespace as an ordinary user; no setuid binaries except newuidmap and newgidmapDeveloper machines, CI runners, multi-user hostsNeeds at least 65,536 subordinate IDs; some networking and storage features differ; shared kernel is still the boundary

OWASP's Docker Security Cheat Sheet makes the same point: rootless mode reduces the impact of a daemon or runtime compromise but does not protect against kernel vulnerabilities, and userns-remap leaves the daemon running as root. Where you need a stronger boundary between tenants, use separate virtual machines.

  • Default container posture: Docker starts containers with a restricted capability allowlist and a default seccomp profile. Do not weaken them globally; add exceptions per service.
  • Daemon-wide defaults in /etc/docker/daemon.json: settings such as "no-new-privileges": true, "userns-remap", "live-restore" and log driver limits apply to every container on the host. Change them in a pilot ring first.
  • Keep the engine current: Docker's port publishing documentation notes that before Engine 28.0.0, hosts on the same layer 2 segment could reach ports published only to localhost.

Secure Compose files

Docker's Compose trust model page lists the fields with security implications when set by an untrusted author. These are the fields to look for in every review, and the ones to block or flag in policy checks.

Compose fieldRiskSafer default
privileged: trueFull access to the hostRemove; add only the specific capability or device needed
cap_addAdds powers such as SYS_ADMIN or NET_RAWcap_drop: [ALL] and add back only what is required
security_optCan disable seccomp or AppArmor (unconfined)Use it to add no-new-privileges:true, not to remove profiles
volumes with host pathsMounts host directories, including /, /etc or the Docker socketNamed volumes; host paths read-only (:ro) and as narrow as possible
network_mode: host, pid: hostShares the host network stack or process namespaceDefault bridge networks; dedicated networks per application tier
devicesExposes host devicesOnly specific devices, never whole /dev
imagePulls and runs any imageImages from your own registry, pinned by digest
env_file, secrets/configs with file:, include, extendsRead files from the host, and the trust model notes their contents can appear in docker compose config outputReview resolved output; keep referenced files inside the project
providerRuns a binary on the host, outside any container, on up, down or stopTreat as host code execution; allow only reviewed providers
ports without a host IPListens on all host addresses"127.0.0.1:8080:80" unless external access is required

A hardened service in Compose typically combines user: set to a non-root UID, read_only: true with tmpfs for scratch space, cap_drop: [ALL], security_opt: ["no-new-privileges:true"], resource limits so one container cannot exhaust the host, and an image pinned by digest. OWASP's cheat sheet recommends the same set: set a user, drop all capabilities and add back only what is needed, use no-new-privileges and run with a read-only filesystem.

Remote and nested Compose files

  • Resolve before you run: docker compose config shows the fully merged configuration, including include, extends, overrides and interpolated variables. A top-level file can look safe while a nested reference adds a privileged service.
  • Pin by digest: Docker's guidance is to reference remote Compose artefacts by digest, because tags are mutable, and to treat a digest update as a code change.
  • Do not accept prompts blindly: Compose warns when a configuration references remote sources; read every reference in the chain.
  • Gate in CI: avoid public or unverified Compose files in pipelines, which often hold cloud tokens and Docker sockets.

Secrets in Compose and builds

Docker's Compose documentation explains the problem with environment variables directly: they are often available to all processes, access is hard to track, and they can be printed in logs when debugging. Secrets avoid this.

  • Runtime secrets: declare them in the top-level secrets: element (source file, environment or external) and grant them per service with the service-level secrets: attribute. Compose bind-mounts each one as a file at /run/secrets/<name>, which allows standard filesystem permissions inside the container. Compose secrets are supported on Linux containers only.
  • _FILE variables: many official images, including mysql and postgres, accept variables such as MYSQL_PASSWORD_FILE=/run/secrets/db_password, so the password never sits in the environment.
  • Build secrets: Docker's build documentation states that build arguments and environment variables are inappropriate for passing secrets because they persist in the final image. Use RUN --mount=type=secret,id=... in the Dockerfile and build: secrets: in Compose, or SSH mounts for Git access.
  • Keep secret source files out of Git: a db_password.txt beside compose.yaml is only safer if it is generated at deploy time, excluded by .gitignore and .dockerignore, and owner-only on the host.
  • Know what Compose secrets are not: with a file: source, the secret is a bind-mounted host file, not an encrypted store. For production, source secrets from a secrets manager at deploy time. See the Secrets Management Guide.

Leaked .env files are one of the most common ways cloud credentials escape: in the exposed .env files campaign, attackers harvested credentials from publicly reachable environment files and used them for cloud extortion. Images are the other route: see the Secrets in Container Images and Build Artefacts Guide.

Networking and port publishing

Docker's port publishing documentation calls publishing container ports "insecure by default": a published port is reachable from outside the host, not just from the host. Two Docker behaviours make this worse than many teams expect:

  • ufw is bypassed: Docker's packet filtering page states that Docker and ufw are incompatible in the way they use firewall rules: published container traffic is diverted in the nat table before ufw's rules apply. A host that looks locked down in ufw status can still expose every published port. Filter in the DOCKER-USER chain or at an upstream firewall instead.
  • Bind addresses: -p 127.0.0.1:8080:80 (or "127.0.0.1:8080:80" in Compose) limits the port to the host. Databases, admin consoles and metrics endpoints should never be published on all addresses.
  • Separate networks per tier: containers on the same user-defined bridge network can reach each other on any port; put the database on an internal network shared only with the application.
  • Forwarding: Docker enables IP forwarding and sets the forward policy to drop unless told otherwise; do not set ip-forward-no-drop unless the host must route.

Where identity fits

Docker hosts hold some of the most powerful non-human credentials in an estate: registry tokens in ~/.docker/config.json, cloud credentials passed to containers, CI tokens on build agents and TLS client certificates for the daemon. Each should be scoped to one job, stored outside the image and rotated.

  • Registry credentials: use a credential helper rather than base64 credentials in config.json, and scoped, expiring tokens for CI. See the Docker Hub Tokens Security Guide.
  • Daemon TLS certificates: a client certificate for a TLS-protected daemon is a root credential for that host. Issue it per person or per pipeline, with short lifetimes.
  • Cloud credentials: prefer workload identity from the platform (instance roles, OIDC federation) to static keys in .env files.
  • The docker group as an entitlement: include it in access reviews alongside sudo rights.

Detection and measurement

  • Daemon events: stream docker events (or the engine API events endpoint) to the SIEM and alert on containers started with --privileged, host namespaces, host root bind mounts or a socket mount.
  • Host audit: auditd watches on /usr/bin/dockerd, /etc/docker/, /var/run/docker.sock and the docker systemd units show tampering and unexpected socket use. See the Linux Logging and auditd for Defenders Guide.
  • External exposure: scan your own address space for TCP 2375 and 2376 and for published database and admin ports.
  • Compose policy checks: lint docker compose config output in CI and fail on the risky fields above.
  • Runtime: eBPF or audit-based sensors catch shells in containers, writes to host paths and calls to the Docker API from inside containers. See the Container Runtime Security Guide.
MetricWhat it showsTarget
Hosts with a TCP daemon listener reachable outside a management networkRemote root exposureZero
Containers with the Docker socket mountedDaemon access from workloadsZero, or documented exceptions on isolated hosts
Running containers as root, privileged or with host namespacesContainer postureFalling; privileged near zero
Members of the docker group per hostPrivileged access sprawlReviewed quarterly; falling
Compose services with secrets in environment:Secret handlingZero in production
Published ports bound to all addresses that should be internalNetwork exposureZero

Implementation roadmap

  1. Inventory: list Docker hosts, engine versions, daemon listeners, docker group members, containers with socket mounts and every published port.
  2. Close remote root first: remove unauthenticated TCP listeners, move remote administration to SSH or TLS client certificates, and remove socket mounts from application containers.
  3. Set safe Compose defaults: publish a reviewed base Compose fragment (non-root user, read_only, cap_drop: [ALL], no-new-privileges, localhost-bound ports) and add a CI check on resolved configuration.
  4. Move secrets: replace secrets in environment: and build arguments with Compose secrets, _FILE variables and build secret mounts.
  5. Pilot daemon changes: try no-new-privileges as a daemon default, userns-remap or rootless mode on a test ring; keep the previous daemon.json under version control as the rollback.
  6. Monitor and review: daemon events in the SIEM, quarterly review of the docker group and exposure scans.

Common failure patterns

  • Daemon on 2375 for "temporary" remote access: the listener stays, the host is found by internet scanners and turned into a miner.
  • Socket mounted for convenience: a monitoring, CI or reverse-proxy container gets /var/run/docker.sock, and a flaw in it becomes host root. A :ro flag on the mount does not help.
  • Database published on 0.0.0.0 behind ufw: the team trusts the host firewall, which Docker's NAT rules bypass.
  • Secrets in environment: and .env committed to Git: credentials appear in docker inspect, logs and the repository history.
  • Build arguments for tokens: an npm or Git token passed with --build-arg persists in the image and is shipped to the registry.
  • Running a remote Compose file unreviewed: a nested include adds a privileged service nobody saw in the top-level file.

Framework mapping

FrameworkReferenceHow it applies
NIST SP 800-190Application Container Security GuideHost, orchestrator, image and container risks and countermeasures
NIST SP 800-53 Rev. 5AC-6 Least Privilege; CM-7 Least Functionality; SC-39 Process Isolation; CM-6 Configuration SettingsSocket and group access, minimal capabilities, isolation modes, hardened defaults
NIST CSF 2.0PR.PS-01; PR.IR-01Configuration management; protecting environments from unauthorised access
MITRE ATT&CK v20T1610 Deploy Container; T1611 Escape to Host; T1552.007 Container API; T1613 Container and Resource Discovery; T1612 Build Image on HostAbuse of exposed daemons and sockets, credential theft from the API, host escape
MITRE D3FENDD3-EI Execution IsolationRootless mode, user namespaces, seccomp and capability limits

Practitioner checklist

  • Remove every unauthenticated TCP daemon listener; use SSH or TLS client certificates on 2376 from a management network only.
  • Treat docker group membership and daemon client certificates as privileged access and review them.
  • Remove /var/run/docker.sock mounts from application containers.
  • Use rootless mode for CI runners and multi-user hosts, or userns-remap where rootless is not practical.
  • Run docker compose config and a policy check on the resolved output before deploying any Compose file; pin remote references by digest.
  • Set non-root user, read_only, cap_drop: [ALL] and no-new-privileges on every service unless an exception is recorded.
  • Pass runtime secrets as Compose secrets and build secrets with --mount=type=secret, never as environment variables or build arguments.
  • Bind internal ports to 127.0.0.1 and filter published ports in DOCKER-USER or upstream, not with ufw alone.
  • Stream daemon events to the SIEM and alert on privileged containers, host mounts and socket mounts.

Standards and references

Related NHI Mgmt Group resources: Container Security Guide · Container Runtime Security Guide · Secrets in Container Images and Build Artefacts Guide · Docker Hub Tokens Security Guide · NIST SP 800-190 Container Security Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 28 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.