Host access

Why Deplo never exposes a Docker socket, and how one grant gates everything that reaches outside a container.

This covers anything that lets a container reach outside its sandbox: bind mounts, devices, host namespaces, foreign networks, and host volumes.

All of it uses one account-level grant: Bind server folders (canMountHostVolumes in the API). It is not a team capability or role.

What the grant protects

A Compose file reaches the agent almost verbatim, so these options can all cross the same host boundary:

  • privileged: true alone is enough to mount the host's disk and chroot into it.
  • pid: host puts nsenter -t 1 one command away.
  • devices: hands over a raw disk.
  • cap_add, security_opt and userns_mode remove the boundary a step at a time.
  • A top-level volumes: entry with external: true attaches an existing volume by its host name. Deplo's volume names are deterministic, so this reached other teams' data, and the control plane's own database.
  • driver_opts: {type: none, device: /, o: bind} is a bind mount of the host, declared one level up from where a service-level check would look.

Compose options that need access

Service keys, twenty-four of them:

privileged, cap_add, devices, device_cgroup_rules, security_opt, cgroup_parent, pid, ipc, uts, network_mode, cgroup, volumes_from, env_file, oom_kill_disable, oom_score_adj, group_add, logging, userns_mode, post_start, pre_stop, deploy, gpus, runtime, extra_hosts.

Most are conditional rather than absolute: oom_score_adj only when negative, pid / ipc / uts / cgroup only on host, container: or service:, runtime only when it is not runc, extra_hosts only for host-gateway, logging only for a driver that is not json-file or local, deploy only its resources.reservations.devices, and post_start / pre_stop only when the hook itself asks for privilege. network_mode is the strictest: it is an allowlist, so only none and default are free and even bridge is gated.

Paths, not keys

Anything that names a file or folder is judged by the path, not by the key: a source that starts with / or climbs with .. is on the server and is gated; a relative one is your app's own file, inside the project directory your stack gets to itself, and is free. That covers a service mount, an env_file, a secrets:/configs: file:, and a build context. So env_file: - .env and file: ./db_pw.txt need no permission, while env_file: - /etc/app.env does.

Top-level blocks

A volumes: entry that is external: true, pins a name:, or uses driver_opts to bind a path, and a top-level secrets: or configs: whose file: names a path on the server.

A networks: entry that joins a network belonging to something else: another Compose project's, or the host's own segment through macvlan or ipvlan, driver: host, any non-empty driver_opts, or a pinned name:. It counts only when a service actually joins it - declaring a network and never attaching it deploys nothing. A network Deplo owns is not one of these: it is rewritten onto your app's own network instead of being refused, so it needs no grant and reaches nothing.

Builds

A build: whose context or Dockerfile is an absolute or .. path, that loads host SSH keys or agents, or that runs privileged.

Merges are refused outright, not gated. extends:, include: and label_file: pull in keys from a file nobody showed Deplo, and those keys can be any of the above. No permission makes them work; inline what they were bringing in.

What a refusal tells you. The message names the key or mount that crossed the boundary and who can grant access.

You are warned before deploying. The deploy card and Compose editor mark the exact option that needs the grant.

Hardening is not gated. cap_drop, read_only, and security_opt: no-new-privileges are allowed.

The value is read, though. no-new-privileges:true is the hardening; no-new-privileges:false turns it off, and that is gated like anything else.

Published ports use a separate grant

canExposePorts covers publishing a container port on the host: an app's Published ports, a ports: entry in a Compose file, and a database's Host port. expose: is deliberately not counted - it publishes nothing, it only documents a port for other containers on the same network.

It is separate because it is a different risk. A published port is reachable from the network, but it does not give the container the machine.

Grant access

Neither grant is a team capability. Find them at Settings -> Users -> the user -> Permissions -> Advanced grants.

An instance admin holds both implicitly, which is why both switches show as on and disabled while that toggle is set.

Grant them to server administrators, not to every member who deploys apps.

Alternatives when access is denied

Nine times out of ten there is a supported answer that does not need the grant:

You wantedDo this instead
A bind mount for uploadsA Volume. Deplo creates and keeps it.
A bind mount for a config fileA File, edited in the browser.
network_mode: host for performanceA domain through Traefik.
A published port so another app can reach itNothing. Apps in one environment, on one server, reach each other by name.
A port for a game server, a mail server or raw TCPPublished ports. That is what the grant is for.
privileged because the README said soCheck whether it is needed. Many images copy it from an older example.

Taking it away

Revoking the grant stops that person from saving anything new that reaches the server - and it also stops the apps they already saved from deploying again. Deplo remembers who authored a Compose file that reaches outside its container, and reads the grant against them on every deploy, including one a git push starts:

shop's compose reaches past its container, and whoever saved it no longer has
permission to let an app reach the server.

Two ways to clear it: give the grant back, or have someone who holds it open the app's Compose and save it again, which makes the reach theirs.

Containers that are already running keep running. Revoking a grant is not a kill switch - it stops the next deploy, it does not stop what is up. Stop the app if that is what you need.

Important limits

  • An empty key declares nothing. cap_add: [] and privileged: false do not trip the gate.
  • Deplo's generated entries do not trip it. The check reads what you authored, not the rendered file.
  • Imports can hit this gate too. Items needing a grant land in the migration report instead of being created.
  • Treat this as host-level access. Someone with this grant can read other teams' data on that machine.

Save or deploy refused for a permission you don't hold? See Sign-in and access troubleshooting.

See also

Did this page help you?

On this page