# 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: ### Volume mode (recommended) ``` 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`) ```yaml 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`) ```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.