Skip to content

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

LevelAllows
viewplan, status, inspect, logs, and mm target swarm nodes
operateview plus restart and job run, cancel, and retry
deployoperate plus deploy and mm team sync
admindeploy 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.

yaml
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 are ssh-ed25519 or sk-ssh-ed25519@openssh.com; RSA and ECDSA are rejected.
  • A key belongs to one user. An environment's own entry overrides *.
  • mm config validate checks the users file, and the host checks it again.
  • mm.overrides.yaml cannot set users, agents, or deploy-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:

sh
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:

yaml
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 machine user cannot have one. When your own keys expire or are removed, your agent's access ends too.
  • mmd access block works 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

sh
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:

sh
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:

text
  access changes in staging:
    carol: none -> deploy, +1 key(s)

To change access without deploying, use mm team sync (needs deploy):

sh
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:

yaml
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:

yaml
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.

sh
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:

sh
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:

sh
mm config validate --for-remote

Only a host admin can make an exception, for one feature of one service, in /etc/mm/host.yaml:

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:

sh
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.