Files
sandcage/docs/ssh.md
T
2026-05-24 19:52:42 +02:00

6.0 KiB

SSH Key Access

Overview

Containers launched by sandcage often need SSH access to clone private repositories or push code. Since the agent user inside the container does not have access to your host SSH keys by default, sandcage provides a setup command that copies or mounts the relevant keys into the container environment.

Modes

Sandcage supports two SSH modes, configured via the ssh_mode field:

ssh_mode: volume

A Docker named volume (sandcage-ssh) is created and populated with only the keys required for your git remotes. The volume is mounted read-only at /home/agent/.ssh inside the container. A synthesized SSH config is written into the volume so the agent can connect to the correct hosts with the correct keys.

Advantages:

  • Only selected keys are copied (not your entire ~/.ssh directory).
  • A minimal SSH config is synthesized from your host config.
  • The volume persists across container restarts without re-reading host files.
  • Works identically on Linux, macOS, and Windows (Docker Desktop).

Bind mode

ssh_mode: bind

Your host ~/.ssh directory is bind-mounted read-only into the container at /home/agent/.ssh:ro. This exposes all files in that directory to the container.

Advantages:

  • Zero setup after initial configuration.
  • Changes to host keys are immediately visible.

Disadvantages:

  • Exposes all keys and config, not just the ones needed.
  • On some platforms, file permission translation can cause issues.

None

ssh_mode: none

No SSH mount is added. Use this if you do not need SSH inside containers or handle key injection through other means.

Setup workflow

Run the interactive setup:

sandcage setup ssh

The command:

  1. Scans ~/.ssh/config (including Include directives) for Host blocks with User git.
  2. Runs git remote -v in the current directory to discover SSH remote hosts.
  3. Merges the two sources and displays discovered host/key pairs.
  4. Prompts you to select which keys to copy.
  5. Asks whether to include known_hosts.
  6. Populates the sandcage-ssh Docker volume with the selected keys and a synthesized SSH config.
  7. Writes the configuration to the appropriate config file.

Flags

Flag Effect
--global Write to ~/.sandcage/config.toml instead of project-local config
--yes Accept all defaults without prompting
--bind Use bind-mount mode instead of volume mode
--refresh Re-populate the volume from existing config (no interactive selection)

Examples

# Interactive setup for current project
sandcage setup ssh

# Non-interactive, global config
sandcage setup ssh --global --yes

# Legacy bind-mount mode
sandcage setup ssh --bind

# Refresh volume after adding a new key to ~/.ssh
sandcage setup ssh --refresh

Configuration

Project config (.sandcage.local.yml or .sandcage.yml)

ssh_mode: volume
ssh_keys:
  - host: github.com
    identity_file: ~/.ssh/id_ed25519
  - host: gitea
    identity_file: ~/.ssh/work_gitea

Global config (~/.sandcage/config.toml)

ssh_mode = "volume"

[[ssh_keys]]
host = "github.com"
identity_file = "~/.ssh/id_ed25519"

[[ssh_keys]]
host = "gitea"
identity_file = "~/.ssh/work_gitea"

Fields

Field Type Description
ssh_mode string One of volume, bind, or none
ssh_keys array List of host/key pairs to include in the volume
ssh_keys[].host string SSH host alias (matches Host line in SSH config)
ssh_keys[].identity_file string Path to the private key (supports ~/ expansion)

Configuration layers are merged in order: global, project, local. Later layers override earlier ones.

Refreshing keys

After adding or rotating SSH keys on the host, update the container volume:

sandcage setup ssh --refresh

This re-reads the existing ssh_keys configuration and the current ~/.ssh/config, then repopulates the sandcage-ssh Docker volume with fresh copies.

For bind mode, no refresh is needed since the host directory is mounted directly.

Security considerations

  • Volume mode copies only the selected private keys into a Docker volume. The volume is mounted read-only into containers. Keys are owned by the agent user with 600 permissions.
  • Bind mode mounts the entire ~/.ssh directory read-only. The container can read all keys and config present in that directory.
  • Neither mode exposes keys to other containers or Docker networks. Keys exist only on the filesystem inside the running container.
  • The sandcage-ssh volume persists on disk until explicitly removed (docker volume rm sandcage-ssh). Run --refresh or delete the volume when decommissioning keys.

Troubleshooting

Volume not found

sandcage: SSH volume not found — run 'sandcage setup ssh --refresh' to populate it

The sandcage-ssh volume does not exist or was removed. Re-run:

sandcage setup ssh --refresh

Or, if no config exists yet:

sandcage setup ssh

Permission denied inside container

If the agent cannot read keys, the volume population step may have failed or the image does not have an agent user at the expected UID. Verify:

docker run --rm -v sandcage-ssh:/home/agent/.ssh:ro sandcage:latest ls -la /home/agent/.ssh

Private keys should be 600 and owned by agent:agent.

No git SSH hosts found

sandcage: no git SSH hosts found in remotes or ~/.ssh/config

This means:

  • The current directory has no SSH git remotes (all remotes use HTTPS).
  • ~/.ssh/config has no Host blocks with User git.

Add an SSH remote or configure your SSH config, then re-run setup.

Migration from legacy bind mount

If your config contains a raw ~/.ssh:/home/agent/.ssh:ro entry in mounts without an ssh_mode field, sandcage detects this as a legacy configuration and offers to migrate to volume mode. Accepting the migration removes the old mount entry and writes ssh_mode: volume with discovered keys. Declining sets ssh_mode: bind explicitly to suppress future prompts.