Manage remote access
Declare who may reach each remote environment in the repo, and apply it on deploy.
Access to remote environments lives in the repo, in the users: key. Each user
lists their public keys and a level per environment. A change to access is an
ordinary commit, reviewed like any other. The host applies it to an environment
when that environment is deployed or synced.
Complete remote host setup first.
Levels
| Level | Allows |
|---|---|
view | plan, status, inspect, logs, and mm target swarm nodes |
operate | view plus restart and job run, cancel, and retry |
deploy | operate plus deploy and mm team sync |
admin | deploy plus exec, forward, secrets, blocks, audit, and Swarm groups, storage, and ingress |
Add-ons grant more without raising the level: exec and forward take true
or a list of services, and secrets: true allows secret metadata and changes.
The host's ceiling in host.yaml caps all of these per environment.
users:
alice:
keys:
- ssh-ed25519 AAAAC3Nza… mm:alice@laptop
access:
staging: deploy
production: view
bob:
keys:
- key: sk-ssh-ed25519@openssh.com AAAAGnNr… mm:bob@yubikey
expires: 2027-01-31
access:
"*": operate # every environment
production: { level: deploy, exec: [api] }
ci:
machine: true
keys: [ssh-ed25519 AAAA… ci@github]
access: { staging: deploy, production: deploy }
Rules:
- User names match
[a-z][a-z0-9-]*. Keys aressh-ed25519orsk-ssh-ed25519@openssh.com; RSA and ECDSA are rejected. - A key belongs to one user. An environment's own entry overrides
*. mm config validatechecks the users file, and the host checks it again.mm.overrides.yamlcannot setusers,agents, ordeploy-from.
To give access its own reviewers, move it to its own file with
mm config split users and list .config/mm/users.yaml in CODEOWNERS.
Agent keys
An AI agent reaches a remote environment with its own key, never yours. Create it and add it to your entry:
mm key create --agent
mm team join --agent # reviewed edit of the users file
mm team join --agent --plan PLAN_HASH --yes
The entry gains a key marked agent: true:
users:
alice:
keys:
- ssh-ed25519 AAAAC3Nza… mm:alice@laptop
- key: ssh-ed25519 AAAAC3Nza… mm-agent:alice@laptop
agent: true
access:
staging: deploy
production: view
The host gives an agent key your access limited by the
agents: block of the deployed commit, and
by the host's ceiling. An agent key never gets admin, forward, or
secrets, and it gets exec only for services that both you and the block
allow. Without an agents: entry for the environment it gets view.
- The agent appears as
alice (agent)in plans,mm team whoami, and the audit log. - A user needs a key of their own to have an agent key, and a
machineuser cannot have one. When your own keys expire or are removed, your agent's access ends too. mmd access blockworks on an agent key's fingerprint and leaves your own key working.
An agent key is a plain key file, because an agent cannot touch a security key. Keep your own key on a security key, or otherwise out of the agent's reach: the host can only tell an agent from you by the key it presents.
Join a team
mm key create # once per device
mm team join --request staging=deploy # reviewed edit of the users file
mm team join --request staging=deploy --plan PLAN_HASH --yes
Commit the change and open a pull request. Once it is merged, an admin deploys
or runs mm team sync for each environment. Then check:
mm --environment staging team whoami
Expected result: your name, key fingerprint, level, and the commit whose users
file the environment enforces. mm key create --security-key uses a FIDO2
security key instead of a key file.
To add someone else, or a CI key: mm team add NAME --key 'ssh-ed25519 AAAA…' --access staging=deploy, with --machine for automation. mm team show
lists everyone in the users file.
Apply access changes
Deploying an environment applies the users file of the deployed commit to that environment first, then deploys. The plan shows the access change:
access changes in staging:
carol: none -> deploy, +1 key(s)
To change access without deploying, use mm team sync (needs deploy):
mm --environment production team sync # plans from HEAD
mm --environment production team sync --plan PLAN_HASH --yes
Each environment is reconciled on its own: syncing staging never changes production.
On a Swarm, the change reaches the other managers too, and the command waits for them to confirm. See access on every manager.
Control which commits deploy
deploy-from limits an environment to commits on certain branches:
environments:
production:
target: prod-eu
deploy-from: [main]
A commit must be on one of the listed branches of the origin, and a commit
that changes deploy-from must pass the list in force.
An entry can also be a pattern: * matches any characters within one /
segment and ? matches one character. "*" on its own allows any branch.
For release branches with hotfixes:
environments:
production:
deploy-from: [main, "*.x"] # main, 26.6.x, 26.7.x, …
Without deploy-from, any commit on the origin may be deployed. Use branch
protection on the git host, because whatever reaches an allowed branch is
trusted.
Forward a remote port
To reach a remote database or admin port from your machine, forward it to
localhost. You need the forward add-on for that service.
mm --environment production forward postgres # localhost:5432
mm --environment production forward postgres:5432 --local 15432
mm --environment production forward postgres redis # several at once
Expected result: Forwarding 127.0.0.1:5432 -> production/postgres:5432. Point
your client at the local port; Ctrl-C stops every forward. Without a port, mm
uses the service's http-port, or the container side of its Compose ports or
expose. If the local port is busy, mm picks a free one and prints it.
Each connection travels through the mm protocol to that service's container.
The host never opens SSH forwarding, and mm never connects to host ports or
other machines. Forwards listen on 127.0.0.1 unless you pass --bind.
Save forwards you use often. Saved forwards are private to your machine:
mm --environment production forward save prod-db postgres --local 15432 --auto
mm forward prod-db # run one by name
mm forward up # start every forward saved with --auto
mm forward list
mm forward remove prod-db
Forwarding needs a rootful Docker or Podman host for now; rootless runtimes report that the container is not reachable.
What a deploy may run
Trusting the repo means trusting it inside the host's limits. A remote host
refuses Compose features that would reach the host itself: privileged,
capabilities beyond Docker's defaults, devices, host namespaces (pid, ipc,
uts, userns_mode, cgroup, network_mode: host), cgroup_parent,
unconfined security_opt, non-net.* sysctls, bind or device driver_opts
volumes, external volumes and networks, configs/secrets files outside the
project, and build: (remote hosts never build). Check before you push:
mm config validate --for-remote
Only a host admin can make an exception, for one feature of one service, in
/etc/mm/host.yaml:
projects:
019c527b-6242-7b43-bfd4-bd60e27c1de3:
allow:
- service: monitoring/node-exporter # <stack>/<service>
features: [pid-host]
Remove a developer
Remove the user (mm team remove NAME), merge, and sync or deploy each
environment. Their key stops working there at once, including in sshd.
For immediate effect before the repo catches up, a host admin can block a key:
mmd access block --project PROJECT_ID --environment production SHA256:FINGERPRINT
On a Swarm, run it on every manager: a block applies only to the host where
you set it. The block lifts by itself once a sync no longer lists the key, or
with mmd access unblock. mmd access show lists users, blocks, and deployed
commits for every environment.
On a Swarm manager, mmd swarm converge sets every mm node label from one
declaration; see Set every label at once.
Emergency access
Break-glass keys in host.yaml are admin on every enrolled project. Use one
with MM_SSH_KEY to repair a users file that locks everyone out, then put the
private key back offline.