Skip to content

RBAC & Roles

dockmesh ships with role-based access control as part of the single binary. Five built-in roles form a privilege ladder; custom roles let you carve permissions and scope arbitrarily for larger teams.

Five roles exist out of the box. They are flagged builtin = 1 in the database — the UI will not let you edit or delete them, but the API returns them so you can read and inspect every grant.

RoleTierWhat it covers
Admin (admin)FullEvery permission on every resource: users, roles, agents, system settings, hash-chain audit, system upgrade.
Host-Admin (host-admin)Full ops, no governanceFull read/write on stacks, containers, images, volumes, networks, hosts, backups, alerts, proxy, templates, registries. No user/role management, no system upgrade, audit is view + export but not write. Designed for engineers who own the fleet but not the platform.
Deployer (deployer)Author + runOperator’s grants plus create/update/delete on stacks and create on images/volumes/networks. The “I write compose files and ship them” tier.
Operator (operator)Run existingRead everything; start/stop/restart containers, shell-in, view logs, deploy/stop pre-existing stacks, run image scans. Cannot create new stacks, volumes, networks, or images.
Viewer (viewer)Read-onlyList + inspect every resource, view audit log, view metrics. No mutations of any kind.

Every user has exactly one role. New local users default to viewer; users created via SSO get whatever the provider’s group-mapping rule resolves to (see SSO). Change a user’s role from Users → Users tab → role dropdown (requires users.update).

Permissions follow the resource.verb pattern — one permission per (resource, action) pair, separated so a custom role can grant “view stacks” without “deploy stacks”, or “create networks” without “delete networks”.

The 17 resource families and their permissions:

PermissionWhat it allows
containers.viewList + inspect containers, view metrics
containers.updateStart / stop / restart / pause / unpause / kill
containers.deleteRemove a container
containers.execShell into a container (sensitive — full code exec)
containers.logsStream live logs (sensitive — PII risk)
PermissionWhat it allows
stacks.viewList + inspect stacks
stacks.createWrite a new compose.yaml to disk
stacks.updateEdit an existing compose.yaml
stacks.deleteRemove a stack
stacks.deployDeploy / stop / scale (sensitive — runs containers)
stacks.migrateMove a stack between hosts
stacks.adoptAdopt a Compose project already running on a host
PermissionWhat it allows
volumes.viewList + inspect volumes
volumes.createCreate a named volume
volumes.deleteRemove + prune volumes
volumes.browseWalk the file tree inside a volume (PII risk)
volumes.read_fileRead a single file’s content from a volume
PermissionWhat it allows
networks.viewList + inspect networks
networks.createCreate a Docker network
networks.deleteRemove + prune networks
PermissionWhat it allows
images.viewList images, inspect digests + layers
images.createPull from a registry (= create a local copy)
images.deleteRemove + prune images
images.scanRun a Grype CVE scan
PermissionWhat it allows
registries.viewList configured registries (credentials masked)
registries.createAdd a new registry credential
registries.updateEdit an existing registry credential
registries.deleteRemove a registry credential
PermissionWhat it allows
hosts.viewList hosts and read their status / metrics
hosts.createEnrol a new agent (issue token)
hosts.updateDrain, upgrade, edit an existing host
hosts.deleteRevoke + remove a host
hosts.tagEdit a host’s tags
PermissionWhat it allows
users.viewList + inspect users
users.createCreate a local user
users.updateEdit a user’s email, role, scope tags
users.deleteRemove a user
users.password_resetForce-reset another user’s password
users.suspendDisable a user without deleting them
PermissionWhat it allows
roles.viewList + inspect roles (built-in + custom)
roles.createDefine a new custom role
roles.updateEdit a custom role’s permissions or scope rows
roles.deleteRemove a custom role (built-ins refuse)
PermissionWhat it allows
tokens.viewList your own API tokens
tokens.createCreate an API token for yourself
tokens.deleteRevoke your own API token
tokens.manage_othersAdmin override: list / revoke others’ tokens
PermissionWhat it allows
backups.viewList backup jobs, targets, runs
backups.createDefine a new job or target
backups.updateEdit an existing job or target
backups.deleteRemove a job or target (runs are kept)
backups.restoreRestore a stack from a run (sensitive — overwrites)
PermissionWhat it allows
proxy.viewList proxy routes + see proxy on/off state
proxy.createAdd a new route
proxy.updateEdit an existing route (toggle proxy, change TLS mode)
proxy.deleteRemove a route
PermissionWhat it allows
alerts.viewList rules + channels + history
alerts.createDefine a new rule or channel
alerts.updateEdit an existing rule or channel
alerts.deleteRemove a rule or channel
PermissionWhat it allows
templates.viewList + inspect templates
templates.createDefine a new template
templates.updateEdit an existing template (built-ins refuse)
templates.deleteRemove a template (built-ins refuse)
PermissionWhat it allows
audit.viewRead the tamper-evident audit log + run chain verify
audit.exportStream the full log to an external sink (reserved)
audit.writeInsert synthetic audit rows (admin-only, integrations)
PermissionWhat it allows
system.viewView system metrics + settings
system.updateEdit settings, manage global env vars, edit proxy config
system.upgradeTrigger a server self-update
PermissionWhat it allows
metrics.viewRead /metrics Prometheus endpoint with a scoped token

Total: ~50 permissions. The full enumeration ships in GET /api/v1/roles/permissions so the custom-role editor can render the live list — that endpoint is also the source of truth for any external automation.

Users & Roles → Roles tab lists every role with its permission count. Click the count for any role (including the built-in ones) to open a read-only modal showing exactly which permissions that role holds, grouped by prefix (container.*, stack.*, …).

Users & Roles → Roles tab → New role opens a form with:

  • Name — lowercase identifier, used internally (e.g. devops)
  • Display name — shown in dropdowns (e.g. DevOps Engineer)
  • Permissions — checkbox matrix of the permissions table above, grouped by prefix with a per-group “all” toggle

Custom roles are saved to the database and appear alongside the built-ins in the user-role dropdown and in OIDC provider config.

Permissions answer “what can this role do?”. Scopes answer “which resources can it do it to?”. dockmesh has scopes at two layers, both optional:

  • Role-level scopes (role_scopes table) — define on the role itself, in the Roles tab. Each row is a (scope_type, scope_value) pair where scope_type ∈ {host, stack, host_tag} and scope_value is the host id, the stack name, or the tag string. Multiple entries OR together — a role scoped to host=prod-01 plus stack=monitoring matches either condition. Built-in roles have no scope entries → they apply globally.
  • User-level scope tags (users.scope_tags) — extra restriction layered on top of the role’s scopes. Edit from Users → Users tab → scope icon. Same <scope_type>:<scope_value> syntax (host:local, stack:web-platform, host_tag:prod). An empty list means “use whatever the role allows”.

When a request comes in, dockmesh verifies the permission then walks both scope layers — the request is allowed only if both layers grant it (or are empty, meaning unrestricted at that layer). The check fires in middleware before any mutation runs; bypassing the UI by calling REST directly hits the same gate.

Tag your hosts from Hosts → host detail → edit tags so the host_tag scope type has something to match against.

The common pattern:

  1. Tag hosts by team: team-frontend, team-backend, team-data.
  2. Create a custom role Team-Operator with the relevant permissions (e.g. containers.view/update/exec/logs, stacks.view/deploy, images.view/scan, audit.view) plus a role-scope row host_tag = team-frontend.
  3. Assign that role to the team’s users. No per-user scope tags needed because the role itself already restricts the reach.

Result: engineers in each team see and manage only their own team’s fleet without any per-user tag bookkeeping.

Buttons and forms for actions a user isn’t permitted to take are hidden, not just disabled — the UI reads GET /api/v1/me on login and gates every action through allowed(permission) checks. A Viewer never sees the “Deploy” button; an Operator never sees the “Edit compose” tab.

The backend re-checks the same permission before executing. Scopes are enforced there too: a scoped Operator who pokes at the API with a stack on a forbidden host gets a 403 regardless of what the UI shows.

Every RBAC-relevant action writes a row to the audit log: role changes, permission grants/revokes, scope edits, user create/delete, password resets, 2FA resets. See Audit log for the full coverage and the hash-chain integrity story.