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 listOutput format
Tab-separated columns:
| Column | Description |
|---|---|
| USER | Username that owns the session |
| PROJECT | Project name, or (default) for non-project sessions |
| CONTAINER | Docker container name |
| STATUS | running or grace_period |
| CONNS | Number of active SSH connections to this session |
| AGE | Time since session was created (e.g., 2h30m, 45s) |
| LIFETIME LEFT | Time 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 7h55mWhen no sessions exist:
No active sessions.stop
Stops a session and destroys its container, companion services, and network.
podspawn stop <user[@project]>Argument format
| Format | Meaning |
|---|---|
alice | Stop alice's default (non-project) session |
alice@backend | Stop alice's session for the backend project |
What it destroys
- The session's Docker container
- All companion service containers (postgres, redis, etc.)
- The per-user Docker network
- 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@backendOutput
Destroyed session: alice@backend (container podspawn-alice-backend)If no active session exists for the given user/project:
Error: no active session for alice@backendThis 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
| Flag | Default | Description |
|---|---|---|
--daemon | false | Run as a background cleanup daemon |
--interval | 60s | Cleanup interval in daemon mode (Go duration format) |
What each cleanup pass does
- Expire grace periods -- finds sessions in
grace_periodstatus past their expiry time and destroys them - Enforce max lifetimes -- finds sessions that have exceeded their configured max lifetime and destroys them (regardless of active connections)
- Reconcile orphans -- finds Docker containers labeled
managed-by=podspawnthat 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 30sRunning 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.serviceThis 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.timerThis 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
| Flag | Default | Description |
|---|---|---|
--prometheus | false | Output in Prometheus exposition format |
Human-readable output
Sessions: 3 total (2 running, 1 grace)
Connections: 4 active
Containers: 3 in Docker
Oldest: 2h30m| Metric | Description |
|---|---|
| Sessions | Total tracked sessions, broken down by running and grace period |
| Connections | Sum of active SSH connections across all sessions |
| Containers | Docker containers with the managed-by=podspawn label |
| Oldest | Age 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 9000Monitoring 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.promlist-users
Lists all registered container users by scanning the key directory.
podspawn list-usersExample 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
| Flag | Default | Description |
|---|---|---|
--force | false | Destroy active sessions without confirmation |
Behavior
- Checks the user exists in
/etc/podspawn/keys/ - If the user has active sessions and
--forceis not set, returns an error - With
--force, destroys all active sessions (containers, services, networks) - 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 --forceWith --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 doctorExample 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 failedChecks
Docker daemon
Runs docker info and verifies the Docker daemon is reachable and responds within 5 seconds.
| Status | Meaning |
|---|---|
| pass | Docker is running; reports the server version |
| fail | Docker 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.
| Status | Meaning |
|---|---|
| pass | Socket exists and is accessible |
| warn | Socket exists but is not world-accessible; non-root users need docker group membership |
| fail | Socket 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).
| Status | Meaning |
|---|---|
| pass | OpenSSH found; reports the version string |
| fail | Neither 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.
| Status | Meaning |
|---|---|
| pass | Configuration is valid |
| fail | sshd -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.
| Status | Meaning |
|---|---|
| pass | AuthorizedKeysCommand is configured and points to podspawn |
| warn | Not 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 nobodypodspawn config dir
Checks that /etc/podspawn exists and is a directory.
| Status | Meaning |
|---|---|
| pass | Directory exists |
| fail | Missing 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.
| Status | Meaning |
|---|---|
| pass | Directory exists with correct permissions |
| warn | Directory exists but permissions are not 0700 |
| fail | Directory does not exist |
Fix: Create the directory and set permissions:
sudo mkdir -p /etc/podspawn/keys
sudo chmod 700 /etc/podspawn/keysThe 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.
| Status | Meaning |
|---|---|
| pass | Directory exists and is writable |
| fail | Directory 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/podspawnlock 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.
| Status | Meaning |
|---|---|
| pass | Directory exists |
| fail | Missing 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).
| Status | Meaning |
|---|---|
| pass | 5+ GB free |
| warn | Between 1 GB and 5 GB free; enough to run but tight for pulling new images |
| fail | Less 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.
| Status | Meaning |
|---|---|
| pass | Image is cached; reports the image ID |
| warn | Image 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
- Removes the project's local repository clone from
/var/lib/podspawn/projects/ - Removes the project entry from
projects.yaml - 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 itupdate
Self-updates the podspawn binary to the latest release from GitHub.
podspawn updateFlags
| Flag | Default | Description |
|---|---|---|
--check | false | Only check for updates, don't download |
What it does
- Queries the GitHub API for the latest release tag
- Compares with the current version
- Downloads the correct binary for your OS and architecture
- 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 --checkOutput
current: v0.1.0, latest: v0.2.0
downloading v0.2.0...
updated to v0.2.0If already on the latest:
already on latest version (v0.2.0)