Skip to content

Configuration

Shed uses YAML configuration files for both client and server settings.

Client Configuration

Location: ~/.shed/config.yaml

The client configuration stores server connections and cached shed locations.

servers:
  mini-desktop:
    host: mini-desktop.tailnet.ts.net
    http_port: 8080
    ssh_port: 2222
    added_at: "2026-01-20T10:00:00Z"

  cloud-vps:
    host: vps.tailnet.ts.net
    http_port: 8080
    ssh_port: 2222
    added_at: "2026-01-19T14:00:00Z"

default_server: mini-desktop

# Timeout for shed create and start operations
create_timeout: 30m

sheds:
  codelens:
    server: mini-desktop
    status: running
    updated_at: "2026-01-20T10:30:00Z"

Client Fields

Field Type Description
servers map Configured server connections
servers.<name>.host string Server hostname or IP
servers.<name>.http_port int HTTP API port
servers.<name>.ssh_port int SSH server port
servers.<name>.api_url string HTTPS control-plane URL (e.g. https://host:8443); overrides scheme+host+port. Set by shed server add --https-port.
servers.<name>.tls_cert_fingerprint string Pinned TLS cert fingerprint (sha256:...).
servers.<name>.control_token string Control-scoped bearer token for the CLI/desktop. Auto-written and refreshed by shed server add in secure mode.
servers.<name>.control_token_expires_at timestamp Expiry of control_token; the CLI refreshes near this and on a 401. Auto-managed.
default_server string Default server for commands
sheds map Cached shed locations
create_timeout duration Timeout for create/start operations (default: 10m)

Server Configuration

Locations (checked in order):

  1. ./server.yaml
  2. ~/.config/shed/server.yaml
  3. /etc/shed/server.yaml
name: mini-desktop
http_port: 8080
ssh_port: 2222

mounts:
  claude:
    source: ~/.claude
    target: /home/shed/.claude
    readonly: false

env_file: ~/.shed/env
log_level: info

Server Fields

Field Type Default Description
name string shed-server Server identifier
http_port int 8080 Plain-HTTP API port. Required in open mode; optional in secure mode (secure serves no plain HTTP, so it is omitted from /api/info and the written client config entry).
ssh_port int 2222 SSH server port
bind_address string 127.0.0.1 Interface every listener (HTTP, HTTPS, SSH) binds to. Defaults to loopback in both modes — shed is local-first. Set a specific IP (LAN/tailnet), 0.0.0.0 or * (all IPv4), or :: (all interfaces) to face the network. A non-loopback bind in open mode also requires allow_insecure_exposure: true; secure mode needs no acknowledgment.
allow_insecure_exposure bool false Acknowledge binding a non-loopback bind_address in open mode (no transport security). Required there, ignored in secure mode and for loopback binds.
trusted_proxy bool false Trust the client-supplied X-Forwarded-For header for the request source IP. Only enable behind a reverse proxy that overwrites that header; the default uses the real TCP peer address.
default_backend string detect Backend to use when none is specified (detect, firecracker, vz). detect auto-selects based on platform: vz on macOS, firecracker on Linux.
mounts map {} Host directories to mount into sheds (formerly credentials)
env_file string - Path to environment variables file
log_level string info Logging level (debug, info, warn, error)
extensions object {} Extensions to activate in VMs (see Extensions)
git object - Git behaviour for in-VM clones (see Git)
firecracker object - Firecracker-specific configuration (see below)
vz object - VZ-specific configuration (see below)
auth object - Optional authentication (see SSH authentication)

Note: Only VM backends are supported. Firecracker is available on Linux. VZ is available on macOS Apple Silicon (arm64). The detect backend auto-selects based on platform.

Authentication mode

auth.mode is the headline switch. The default open posture is unchanged — ideal on a tailnet or trusted LAN (plain HTTP, no tokens, no TLS); the SSH allowlist (auth.ssh) is the one independently-tunable layer there. secure is the internet-facing posture: it derives the full bundle (SSH enforce + HTTP tokens + TLS-only) and refuses to start without an SSH key source. Two invariants hold: tokens ⟺ TLS ⟺ secure and https ⟺ secure. See Security and the Security Configuration guide.

Field Type Default Description
auth.mode string open open enforces nothing (plain HTTP only); secure derives SSH-allowlist enforce + HTTP-token enforce + TLS-only (https_port defaults to 8443; no plain-HTTP listener is served), and fails to start without a key source.
auth.token_ttl duration 24h Lifetime of a bootstrap-minted HTTP token. Clients refresh transparently near expiry / on a 401.

The pre-v0.7.1 public_exposure flag and auth.http.tokens list are removed and rejected at startup — set auth.mode: secure and let shed server add mint tokens over SSH. As of v0.7.2 the whole auth.http block is gone (HTTP enforcement derives from auth.mode: secure), and https_port / auth.ssh.mode: enforce are valid only in secure mode — see Upgrading v0.7.1 → v0.7.2. As of v0.7.4 http_bind/ssh_bind/internal_http_port are removed in favour of a single bind_address (loopback by default), and http_port is optional in secure mode — see Upgrading v0.7.3 → v0.7.4.

SSH authentication

auth.ssh gates SSH public-key access against an allowlist. The default (omitted, or mode: off) accepts all keys — the legacy behavior. Identity comes from the offered key, GitHub-style; the username still selects the shed.

Field Type Default Description
auth.ssh.mode string off off accepts all keys; warn logs would-deny attempts but still accepts (use to roll out safely); enforce rejects keys not in the allowlist.
auth.ssh.authorized_keys list [] Inline OpenSSH authorized_keys lines.
auth.ssh.authorized_keys_file string - Path to an authorized_keys-format file.
auth.ssh.github_users list [] Seed the allowlist from https://github.com/<user>.keys. Keys are cached to disk and fail closed to the last-known-good cache if GitHub is unreachable.
auth.ssh.github_refresh duration 1h How often to re-fetch GitHub keys.
auth.ssh.max_auth_tries int 6 Public-key attempts allowed per connection.

With mode: enforce, the server refuses to start if no keys resolve (empty inline/file and a failed GitHub fetch with no cache) — use warn for first boot, or provide inline keys.

auth:
  mode: secure               # open (default) | secure — secure forces ssh enforce
  ssh:
    github_users: [charliek]  # a key source is required in secure mode

TLS and network surface

TLS and network-surface fields. HTTP token enforcement is not an independent field — it is derived from auth.mode: secure (there is no auth.http block). secure turns on TLS for you, and https_port is valid only in secure mode. See Security and the Security Configuration guide.

Field Type Default Description
https_port int 8443 in secure Serve HTTPS with a self-signed, client-pinned cert, bound to bind_address. Requires auth.mode: secure (rejected in open mode); defaults to 8443 there. In secure mode this is the only listener — no plain HTTP is served.
tls_names list [] Extra hostnames/IPs added as cert SANs. localhost, 127.0.0.1, ::1 are always included.
tls_cert_file / tls_key_file string next to host key Override where the TLS cert + key are persisted.
bind_address string 127.0.0.1 Interface every listener binds to (loopback by default in both modes). Set a specific IP, 0.0.0.0/* (all IPv4), or :: (all interfaces) to face the network. A non-loopback bind in open mode also requires allow_insecure_exposure: true.
allow_insecure_exposure bool false Acknowledge a non-loopback bind_address in open mode (no transport security). Ignored in secure mode.
trusted_proxy bool false Trust X-Forwarded-For (only behind a proxy that overwrites it).

Mounts

Mounts are directories from the host that are shared with sheds. The method depends on the backend:

  • Firecracker: Mounted via 9P over the TAP bridge network.
  • VZ: Mounted via VirtioFS.

Both mechanisms provide live filesystem sharing -- changes on either side are immediately visible to the other.

Renamed from credentials

This section was previously named credentials. The mounts key has the identical shape. The deprecated credentials key still works as a fallback when mounts is absent, but new configs should use mounts.

mounts:
  name:
    source: /host/path      # Path on the host (~ supported, must be a directory)
    target: /container/path  # Path inside shed
    readonly: true           # Optional, default false

Mount sources must be directories. Single-file mounts are not supported. For individual config files like .gitconfig, use shed sync to push them as dotfiles. For SSH-based git authentication, use the shed-extensions SSH agent forwarding instead of mounting ~/.ssh.

Missing sources: If a mount's source path does not exist on the host, it is skipped with a log warning. Create the source directory on the host before starting the shed.

Common mounts:

mounts:
  # Claude Code config (needs write for token refresh)
  claude:
    source: ~/.claude
    target: /home/shed/.claude
    readonly: false

  # GitHub CLI
  gh:
    source: ~/.config/gh
    target: /home/shed/.config/gh
    readonly: true

  # AWS credentials
  aws:
    source: ~/.aws
    target: /home/shed/.aws
    readonly: true

  # GCP credentials
  gcloud:
    source: ~/.config/gcloud
    target: /home/shed/.config/gcloud
    readonly: true

Exclude Patterns

The mount config accepts an exclude field with glob patterns. This field is currently accepted but has no effect on VM backends -- VirtioFS and 9P mount entire directories. Exclude patterns are used by shed sync path mappings. The field is retained for forward compatibility.

mounts:
  claude:
    source: ~/.claude
    target: /home/shed/.claude
    readonly: false
    exclude:
      - "*.db"
      - "*.db-shm"
      - "*.db-wal"
      - "log/*"
      - "storage/*"

Extensions

Extensions are activated per-VM by listing their namespace names. The agent reads manifests from /etc/shed-extensions.d/ in the VM image and enables the matching systemd units at startup. When extensions is omitted, no extensions are activated.

extensions:
  enabled:
    - ssh-agent
    - aws-credentials
    - docker-credentials

See Extensions for the full guide on the message bus, manifests, SDK, and health reporting.

Git

When a shed is created with a repo whose URL uses SSH (e.g., git@github.com:org/repo.git or ssh://git@host/path), the server seeds the in-VM ~/.ssh/known_hosts before invoking git clone. Without this, OpenSSH defaults to StrictHostKeyChecking=yes and rejects the connection with Host key verification failed. The owner/repo shorthand is expanded to git@github.com:owner/repo.git, so it goes through the same SSH path.

GitHub's published host keys (ED25519, ECDSA, RSA) are baked into the server binary and are always included. To trust additional hosts (GitLab, GitHub Enterprise, self-hosted Gitea, etc.), add their known_hosts lines to git.extra_known_hosts:

git:
  extra_known_hosts:
    - "gitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf"
    - "my-gitea.internal ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI..."

Each entry must be a syntactically valid known_hosts line: <host> <key-type> <base64-key>. The server validates this at startup and refuses to start if any entry is malformed.

Generating entries: Run ssh-keyscan <host> on a trusted machine and paste the output. For example:

ssh-keyscan gitlab.com 2>/dev/null

The git.extra_known_hosts list is additive — it extends the built-in defaults, never replaces them. Only SSH-form clone URLs (git@host:path, ssh://...) consult known_hosts; HTTPS clones skip this step entirely.

Key rotation: If GitHub or another host rotates its keys, you can extend the trust list via extra_known_hosts immediately without waiting for a shed release. The default GitHub keys ship with the server binary and are updated in releases.

Image references and ${shed.version}

default_image and image_aliases are optional. On a release build of shed-server, when they are unset they are synthesized from the server's own version: default_image becomes ghcr.io/charliek/shed-<backend>-full:vX.Y.Z and the base / extensions / full aliases resolve to the matching tags. So upgrading shed-server moves new sheds to the new images with no config edit.

Set them explicitly only to pin a custom or mirrored registry. To track the server version without hand-editing the tag on every upgrade, use the ${shed.version} token — it expands to the running server's release tag (vX.Y.Z) at config load:

vz:
  default_image: myregistry.example.com/shed-vz-full:${shed.version}

Why this is version-coupled: the base images bundle the in-VM shed-agent and the shed-extensions guest binaries, which are versioned in lockstep with shed. Pinning the image to the server version — via the default synthesis or the ${shed.version} token — keeps the guest components compatible with the server instead of drifting on upgrade.

${shed.version} is the only accepted token

A literal {version} or v{version} is not expanded. On a dev/unreleased build the token has nothing to resolve to, so set concrete refs (or leave them unset and pass --image on each create).

Egress Control

Opt-in, audit-first egress filtering (off unless enabled: true). A bad glob or CEL rule fails server start. See Egress Control for the full model, the policy language, and the (important) honest security posture.

egress:
  enabled: true
  port_range: "20000-30000"   # per-shed listener allocation (default)
  default: []                 # profiles for sheds created without --egress ([] = none)
  profiles:
    github:   { allow: ["*.github.com", "github.com"] }
    internal: { rule: 'host.endsWith(".corp.internal") && port == 443' }
Field Default Description
enabled false Master switch. When off, the proxy child is never started and sheds get no egress control.
port_range 20000-30000 Per-shed listener port allocation range.
default [] Profiles applied to sheds created without --egress. []/absent = no egress for those sheds.
profiles {} Named policy fragments (allow/deny globs, mode: audit, or a CEL rule). Names off/none/default are reserved.

Firecracker Configuration

When enabling Firecracker, configure the Firecracker-specific settings:

default_backend: firecracker

firecracker:
  default_image: ghcr.io/charliek/shed-fc-full:${shed.version}
  image_aliases:
    base: ghcr.io/charliek/shed-fc-base:${shed.version}
    extensions: ghcr.io/charliek/shed-fc-extensions:${shed.version}
    full: ghcr.io/charliek/shed-fc-full:${shed.version}
  pull_policy: missing
  images_dir: /var/lib/shed/firecracker/images
  instance_dir: /var/lib/shed/firecracker/instances
  socket_dir: /var/run/shed/firecracker
  default_cpus: 2
  default_memory_mb: 4096
  default_disk_gb: 20
  vsock_base_cid: 100
  console_port: 1024
  notify_port: 1026
  start_timeout: 120s
  stop_timeout: 10s
  bridge_name: shed-br0
  bridge_cidr: 172.30.0.1/24
  tap_prefix: shed-tap

The ${shed.version} token above resolves to the running server's release tag — see Image references and ${shed.version}. These fields are optional; left unset on a release build they are synthesized from the server version.

Firecracker Fields

Field Type Default Description
kernel_path string "" Optional. Path to a Linux kernel image (auto-populated by published-image pulls). The Phase B initramfs prefers the kernel embedded in the image blob ({images_dir}/blobs/sha256/<digest>/kernel); this path is the fallback for legacy blobs that lack an embedded kernel.
default_image string "" Path or Docker ref used for new sheds when shed create runs without --image. Empty is fine if every create passes --image; otherwise shed create errors with INVALID_REQUEST: no --image specified and no default_image configured.
image_aliases map - Optional short alias → Docker ref (or path) map for shed create --image <alias>. Listings always show the resolved ref, not the alias.
pull_policy string missing missing (use cache, pull if absent), always (always pull), or never (error if not cached). Ignored for local-path images.
images_dir string /var/lib/shed/firecracker/images Directory for the content-addressed blob store and the ref→digest index
upper_size_default string 5G Default logical size of the per-shed writable overlay upper layer when shed create --upper-size is omitted. Validated to the range 1–100 GiB.
instance_dir string - Directory for VM instances
socket_dir string - Directory for API/vsock sockets
default_cpus int 2 Default vCPUs per VM
default_memory_mb int 4096 Default memory per VM (MB)
default_disk_gb int 20 Default disk size per VM (GB)
vsock_base_cid int 100 Starting CID for vsock guest addressing
console_port int 1024 Vsock port for VM console I/O
notify_port int 1026 Vsock port for the message channel (health checks, plugins)
start_timeout duration 30s VM startup timeout
stop_timeout duration 10s Graceful shutdown timeout
guest_mtu int 0 Guest primary-interface MTU. 0 (default) auto-detects the host egress path MTU at VM start and lowers the guest to match a reduced path (e.g. a VPN/overlay), otherwise leaves it at 1500. Set 12801500 to pin a value when detection misses. See note below.
bridge_name string shed-br0 Linux bridge name
bridge_cidr string 172.30.0.1/24 Bridge network CIDR
tap_prefix string shed-tap TAP device name prefix

Path-existence validation only fires when all configured image sources are local paths. When default_image (or any image_aliases entry) is a Docker ref, the path-existence check is skipped because the file is created on first pull. kernel_path is still required to point at a real file when set non-empty.

See Firecracker Setup for setup details.

VZ Configuration

When enabling the VZ backend on macOS Apple Silicon, configure the VZ-specific settings:

Image values in default_image and image_aliases can be either ext4 file paths or Docker image references. Docker refs are auto-pulled and converted on first use.

default_backend: vz

vz:
  vfkit_path: vfkit
  kernel_path: ~/Library/Application Support/shed/vz/vmlinux
  initrd_path: ~/Library/Application Support/shed/vz/initrd.img
  default_image: ghcr.io/charliek/shed-vz-full:${shed.version}
  image_aliases:
    base: ghcr.io/charliek/shed-vz-base:${shed.version}
    extensions: ghcr.io/charliek/shed-vz-extensions:${shed.version}
    full: ghcr.io/charliek/shed-vz-full:${shed.version}
  pull_policy: missing
  images_dir: ~/Library/Application Support/shed/vz/
  instance_dir: ~/Library/Application Support/shed/vz/instances
  socket_dir: ~/.shed/vz/sockets
  default_cpus: 2
  default_memory_mb: 4096
  default_disk_gb: 20
  console_port: 1024
  notify_port: 1026
  tcp_proxy_port: 1028
  start_timeout: 60s
  stop_timeout: 10s

VZ Fields

Field Type Default Description
vfkit_path string vfkit Path to vfkit binary
kernel_path string "" Optional. Path to a decompressed Linux kernel (auto-populated by published-image pulls). Phase B prefers the kernel embedded in the image blob; this is the fallback for legacy blobs that lack an embedded kernel.
initrd_path string "" Optional. Path to an initial RAM disk image. The shed-overlay initramfs lives inside the image blob, so this field is only consulted for legacy blobs.
default_image string "" Path or Docker ref used for new sheds when shed create runs without --image. Empty is fine if every create passes --image; otherwise shed create errors with INVALID_REQUEST: no --image specified and no default_image configured.
image_aliases map - Optional short alias → Docker ref (or path) map for shed create --image <alias> (see Images). Listings always show the resolved ref.
pull_policy string missing missing (use cache, pull if absent), always (always pull), or never (error if not cached). Ignored for local-path images.
images_dir string ~/Library/Application Support/shed/vz/ Directory for the content-addressed blob store and the ref→digest index
upper_size_default string 5G Default logical size of the per-shed writable overlay upper layer when shed create --upper-size is omitted. Validated to the range 1–100 GiB.
instance_dir string - Directory for VM instances
socket_dir string - Directory for vsock Unix sockets (must not contain spaces)
default_cpus int 2 Default vCPUs per VM
default_memory_mb int 4096 Default memory per VM (MB)
default_disk_gb int 20 Default disk size per VM (GB)
console_port int 1024 Vsock port for VM console I/O
notify_port int 1026 Vsock port for the message channel (health checks, plugins)
tcp_proxy_port int 1028 Vsock port for TCP proxy (used by DialService for tunnels and Connect API)
start_timeout duration 60s VM startup timeout
stop_timeout duration 10s Graceful shutdown timeout
guest_mtu int 0 Guest primary-interface MTU. 0 (default) auto-detects the host egress path MTU at VM start and lowers the guest to match a reduced path (e.g. a VPN), otherwise leaves it at 1500. Set 12801500 to pin a value when detection misses. See note below.

Guest MTU and VPNs

Behind a VPN (or any path whose MTU is below 1500), full-size guest packets can be silently dropped — most visibly, docker pull fails with a TLS handshake timeout while curl to the same registry works. With guest_mtu: 0 (the default), shed-server detects the host's egress path MTU when a shed starts and lowers the guest interface to match; on a normal 1500 path nothing changes.

Detection runs at VM start, so if you connect or disconnect the VPN while a shed is already running, run shed restart <name> to re-detect. Set guest_mtu explicitly only to override detection (for example, to pin a value for a VPN whose tunnel interface still reports 1500).

See VZ Setup for setup details.

Environment File

Location: As configured in env_file (typically ~/.shed/env)

Environment variables injected into all containers:

ANTHROPIC_API_KEY=sk-ant-...
OPENAI_API_KEY=sk-...
GITHUB_TOKEN=ghp_...

Set restricted permissions:

chmod 600 ~/.shed/env

SSH Known Hosts

Location: ~/.shed/known_hosts

Stores SSH host keys for shed servers. Populated automatically when running shed server add.

Sync Configuration

See File Sync for sync configuration.

Tunnel Configuration

See Tunnels for tunnel configuration.