podspawnpodspawn

Admin Commands

CLI reference for podspawn session management and monitoring commands

Admin commands help operators monitor sessions, stop containers, run cleanup, and check system health.

list

Lists all active sessions tracked in the state database.

podspawn list

Output format

Tab-separated columns:

ColumnDescription
USERUsername that owns the session
PROJECTProject name, or (default) for non-project sessions
CONTAINERDocker container name
STATUSrunning or grace_period
CONNSNumber of active SSH connections to this session
AGETime since session was created (e.g., 2h30m, 45s)
LIFETIME LEFTTime until max lifetime expires, or expired

Example output

USER     PROJECT     CONTAINER                 STATUS        CONNS  AGE     LIFETIME LEFT
alice    backend     podspawn-alice-backend    running       2      1h30m   6h30m
bob      (default)   podspawn-bob              grace_period  0      45m     7h15m
carol    frontend    podspawn-carol-frontend   running       1      5m      7h55m

When no sessions exist:

No active sessions.

stop

Stops a session and destroys its container, companion services, and network.

podspawn stop <user[@project]>

Argument format

FormatMeaning
aliceStop alice's default (non-project) session
alice@backendStop alice's session for the backend project

What it destroys

  1. The session's Docker container
  2. All companion service containers (postgres, redis, etc.)
  3. The per-user Docker network
  4. The session record in the state database

Examples

# Stop alice's default session
podspawn stop alice

# Stop alice's backend project session
podspawn stop alice@backend

Output

Destroyed session: alice@backend (container podspawn-alice-backend)

If no active session exists for the given user/project:

Error: no active session for alice@backend

This immediately destroys the container. Any unsaved work inside the container is lost. Connected SSH sessions will be terminated.


cleanup

Reconciles orphaned containers and enforces time-to-live limits. Runs a single pass by default, or loops continuously in daemon mode.

podspawn cleanup [flags]

Flags

FlagDefaultDescription
--daemonfalseRun as a background cleanup daemon
--interval60sCleanup interval in daemon mode (Go duration format)

What each cleanup pass does

  1. Expire grace periods -- finds sessions in grace_period status past their expiry time and destroys them
  2. Enforce max lifetimes -- finds sessions that have exceeded their configured max lifetime and destroys them (regardless of active connections)
  3. Reconcile orphans -- finds Docker containers labeled managed-by=podspawn that have no corresponding database record and removes them

Grace periods are processed first so that max-lifetime enforcement doesn't attempt to destroy the same session twice.

Examples

# Single cleanup pass
podspawn cleanup

# Run as a daemon with default 60s interval
podspawn cleanup --daemon

# Custom interval
podspawn cleanup --daemon --interval 30s

Running with systemd

Podspawn ships systemd units in the deb/rpm packages. You can also use them manually:

Option A: Persistent daemon (recommended)

# Shipped at /etc/systemd/system/podspawn-cleanup.service
sudo systemctl enable --now podspawn-cleanup.service

This runs podspawn cleanup --daemon --interval 60s as a long-lived process.

Option B: Timer-based (alternative)

# Shipped at /etc/systemd/system/podspawn-cleanup.timer
sudo systemctl enable --now podspawn-cleanup.timer

This runs a single cleanup pass every 60 seconds via systemd's timer infrastructure.

The cleanup command is not in the critical path. The system works without it, just with slightly delayed cleanup. Orphaned containers from crashes will be caught on the next pass.


status

Shows system-level metrics about sessions and containers. Supports both human-readable and Prometheus exposition formats.

podspawn status [flags]

Flags

FlagDefaultDescription
--prometheusfalseOutput in Prometheus exposition format

Human-readable output

Sessions:    3 total (2 running, 1 grace)
Connections: 4 active
Containers:  3 in Docker
Oldest:      2h30m
MetricDescription
SessionsTotal tracked sessions, broken down by running and grace period
ConnectionsSum of active SSH connections across all sessions
ContainersDocker containers with the managed-by=podspawn label
OldestAge of the longest-running session

Prometheus output

podspawn status --prometheus
# HELP podspawn_sessions_total Total tracked sessions
# TYPE podspawn_sessions_total gauge
podspawn_sessions_total 3
# HELP podspawn_sessions_running Sessions in running state
# TYPE podspawn_sessions_running gauge
podspawn_sessions_running 2
# HELP podspawn_sessions_grace Sessions in grace period
# TYPE podspawn_sessions_grace gauge
podspawn_sessions_grace 1
# HELP podspawn_connections_total Total active SSH connections
# TYPE podspawn_connections_total gauge
podspawn_connections_total 4
# HELP podspawn_containers_docker Docker containers with managed-by=podspawn
# TYPE podspawn_containers_docker gauge
podspawn_containers_docker 3
# HELP podspawn_oldest_session_seconds Age of the oldest session in seconds
# TYPE podspawn_oldest_session_seconds gauge
podspawn_oldest_session_seconds 9000

Monitoring integration

Scrape the Prometheus output with a textfile collector or a cron job:

# Write metrics for node_exporter textfile collector
podspawn status --prometheus > /var/lib/prometheus/node-exporter/podspawn.prom

list-users

Lists all registered container users by scanning the key directory.

podspawn list-users

Example output

alice  (3 keys)
ci-runner  (1 key)
deploy  (2 keys)

remove-user

Removes a container user by deleting their key file and optionally destroying their active sessions.

podspawn remove-user <username> [flags]

Flags

FlagDefaultDescription
--forcefalseDestroy active sessions without confirmation

Behavior

  1. Checks the user exists in /etc/podspawn/keys/
  2. If the user has active sessions and --force is not set, returns an error
  3. With --force, destroys all active sessions (containers, services, networks)
  4. Removes the key file

Examples

# Remove user with no active sessions
podspawn remove-user alice

# Force-remove user with active sessions
podspawn remove-user alice --force

With --force, all active sessions are immediately destroyed. Connected SSH sessions will be terminated and unsaved work lost.


doctor

Runs a series of preflight checks to verify that the server is correctly configured for podspawn. Useful after initial setup, before upgrades, or when debugging connection issues.

podspawn doctor

Example output

  [pass] Docker daemon
  [pass] Docker socket
  [pass] OpenSSH version
  [pass] sshd config valid
  [pass] AuthorizedKeysCommand
  [pass] podspawn config dir
  [pass] key directory
  [pass] state directory
  [pass] lock directory
  [pass] disk space
  [warn] default image: ubuntu:24.04 not cached; first connection will pull it (slow)

10 passed, 1 warned, 0 failed

Checks

Docker daemon

Runs docker info and verifies the Docker daemon is reachable and responds within 5 seconds.

StatusMeaning
passDocker is running; reports the server version
failDocker is not running, not installed, or not in PATH

Fix: Install Docker and ensure the daemon is running (systemctl start docker).

Docker socket

Checks that /var/run/docker.sock exists and has appropriate permissions.

StatusMeaning
passSocket exists and is accessible
warnSocket exists but is not world-accessible; non-root users need docker group membership
failSocket does not exist

Fix: If the socket is missing, start Docker. If permissions are wrong, add the podspawn user to the docker group (usermod -aG docker <user>).

OpenSSH version

Detects the installed OpenSSH version by running sshd -V (falling back to ssh -V).

StatusMeaning
passOpenSSH found; reports the version string
failNeither sshd nor ssh found

Fix: Install openssh-server. Podspawn requires OpenSSH 7.4+ for the restrict keyword in authorized_keys.

sshd config valid

Runs sshd -t to validate the current sshd configuration file.

StatusMeaning
passConfiguration is valid
failsshd -t reported errors

Fix: Check the sshd_config syntax errors reported by sshd -t and correct them. Common causes: duplicate directives, invalid keywords, bad Match blocks.

AuthorizedKeysCommand

Reads the sshd_config file and checks for an AuthorizedKeysCommand directive that references podspawn.

StatusMeaning
passAuthorizedKeysCommand is configured and points to podspawn
warnNot configured; sshd will not invoke podspawn for key lookups

Fix: Run podspawn server-setup to configure sshd automatically, or add the lines manually:

AuthorizedKeysCommand /usr/local/bin/podspawn auth-keys %u %t %k
AuthorizedKeysCommandUser nobody

podspawn config dir

Checks that /etc/podspawn exists and is a directory.

StatusMeaning
passDirectory exists
failMissing or not a directory

Fix: Run podspawn server-setup, which creates all required directories.

key directory

Checks that the key directory (/etc/podspawn/keys/ by default) exists and has permissions set to 0700.

StatusMeaning
passDirectory exists with correct permissions
warnDirectory exists but permissions are not 0700
failDirectory does not exist

Fix: Create the directory and set permissions:

sudo mkdir -p /etc/podspawn/keys
sudo chmod 700 /etc/podspawn/keys

The key directory stores user public keys. Permissions looser than 0700 allow other users on the system to read or modify SSH keys, which is a security risk.

state directory

Checks that /var/lib/podspawn exists and is writable. The check creates and removes a temporary file to verify write access.

StatusMeaning
passDirectory exists and is writable
failDirectory is missing, not a directory, or not writable

Fix: Create the directory with appropriate ownership:

sudo mkdir -p /var/lib/podspawn
sudo chown root:root /var/lib/podspawn

lock directory

Checks that /var/lib/podspawn/locks/ exists and is a directory. This directory holds per-user file locks that prevent race conditions during container creation.

StatusMeaning
passDirectory exists
failMissing or not a directory

Fix: sudo mkdir -p /var/lib/podspawn/locks

disk space

Checks free disk space on the partition containing /var/lib/podspawn (falls back to / if that path does not exist).

StatusMeaning
pass5+ GB free
warnBetween 1 GB and 5 GB free; enough to run but tight for pulling new images
failLess than 1 GB free

Fix: Free up disk space. Docker images are typically the biggest consumer. Run docker system prune or docker image prune -a to remove unused images.

default image

Checks whether the configured default image (e.g., ubuntu:24.04) is already cached locally in Docker.

StatusMeaning
passImage is cached; reports the image ID
warnImage is not cached; the first connection for a user without a Podfile will trigger a pull, which can take 30+ seconds

Fix: Pre-pull the image: docker pull ubuntu:24.04


remove-project

Deregisters a project and removes its local clone.

podspawn remove-project <name>

Behavior

  1. Removes the project's local repository clone from /var/lib/podspawn/projects/
  2. Removes the project entry from projects.yaml
  3. Does NOT remove the Docker image (prints a hint to docker rmi)

Example

podspawn remove-project backend
# removed project backend
# note: image podspawn/backend:podfile-abc123 still exists; run 'docker rmi ...' to remove it

update

Self-updates the podspawn binary to the latest release from GitHub.

podspawn update

Flags

FlagDefaultDescription
--checkfalseOnly check for updates, don't download

What it does

  1. Queries the GitHub API for the latest release tag
  2. Compares with the current version
  3. Downloads the correct binary for your OS and architecture
  4. Replaces the current binary atomically

Active SSH sessions are not affected by the update. The new binary is used for the next spawn invocation.

Examples

# Update to latest
podspawn update

# Check without downloading
podspawn update --check

Output

current: v0.1.0, latest: v0.2.0
downloading v0.2.0...
updated to v0.2.0

If already on the latest:

already on latest version (v0.2.0)

On this page