# Uninstall and maintenance

> uninstall.sh flags (--images, --data, --all), managed-container cleanup, workspace retention defaults, docker compose ps/logs/restart sandboxd, and backup paths for SQLite and workspaces.

- Repository: tastyeffectco/sandboxes
- GitHub: https://github.com/tastyeffectco/sandboxes
- Human docs: https://grok-wiki.com/public/docs/tastyeffectco-sandboxes-f551c1a2e9a0
- Complete Markdown: https://grok-wiki.com/public/docs/tastyeffectco-sandboxes-f551c1a2e9a0/llms-full.txt

## Source Files

- `uninstall.sh`
- `install.sh`
- `AGENTS.md`
- `docker-compose.yml`
- `ARCHITECTURE.md`

---

---
title: "Uninstall and maintenance"
description: "uninstall.sh flags (--images, --data, --all), managed-container cleanup, workspace retention defaults, docker compose ps/logs/restart sandboxd, and backup paths for SQLite and workspaces."
---

`./uninstall.sh` tears down the Compose stack (`traefik`, `sandboxd`), removes every sandbox container labeled `sandboxed.managed=true`, and optionally drops built images or the entire `SANDBOXED_DATA_DIR` tree. By default it keeps workspaces, SQLite state, logs, and the git checkout; day-to-day maintenance uses `docker compose` from the repository root.

## Uninstall script

Run `./uninstall.sh` from the repository root (same directory as `docker-compose.yml`). The script loads `.env` when present, mirrors `install.sh` Docker/sudo detection, and never deletes the checkout itself.

| Invocation | Stack | Managed sandboxes | Network | Images | Data dir |
|---|---|---|---|---|---|
| `./uninstall.sh` | `compose down` | `docker rm -f` (label filter) | remove if lingering | kept | **kept** |
| `./uninstall.sh --images` | same | same | same | remove `SANDBOXED_IMAGE` + `sandboxed-control-plane:1.0.0` | kept |
| `./uninstall.sh --data` | same | same | same | kept | **deleted** (prompt) |
| `./uninstall.sh --all` | same | same | same | removed | **deleted** (prompt) |
| `./uninstall.sh --all --yes` | same | same | same | removed | deleted (no prompt) |

<ParamField body="--images" type="flag">
Removes `sandboxed-base:1.0.0` (or `SANDBOXED_IMAGE` from `.env`) and `sandboxed-control-plane:1.0.0`. Idempotent if images are already gone.
</ParamField>

<ParamField body="--data" type="flag">
Runs `rm -rf` on `SANDBOXED_DATA_DIR` (default `/var/lib/sandboxed`), including workspaces, SQLite, snapshots, templates, and logs under that tree. Requires typing `yes` unless combined with `--yes` / `-y`.
</ParamField>

<ParamField body="--all" type="flag">
Equivalent to `--images` plus `--data`.
</ParamField>

<ParamField body="--yes" type="flag">
Skips the destructive confirmation when `--data` or `--all` is set.
</ParamField>

### Execution order

<Steps>
<Step title="Stop the Compose stack">
`docker compose down --remove-orphans` stops `traefik` and `sandboxd`. A non-running stack is reported and skipped.
</Step>
<Step title="Remove managed sandbox containers">
Containers are selected only with `--filter label=sandboxed.managed=true`, then force-removed. Other Docker workloads on the host are untouched.
</Step>
<Step title="Remove the Docker network">
`SANDBOXED_NETWORK` (default `sandboxed_net`) is removed if it still exists.
</Step>
<Step title="Optional image and data removal">
`--images` and/or `--data` run last. Without `--data`, the script prints that data was kept at `SANDBOXED_DATA_DIR`.
</Step>
</Steps>

<Note>
After a default uninstall, re-run `./install.sh` to rebuild images (if removed), recreate the data dir if needed, and bring the stack back. A full `--all` uninstall suggests you may delete the repo folder manually; the script does not do that for you.
</Note>

## What uninstall removes vs keeps

```text
Host (outside repo checkout)
├── git checkout/              ← never touched by uninstall.sh
├── Docker: traefik, sandboxd  ← compose down
├── Docker: s-<ulid> …         ← label sandboxed.managed=true
├── network sandboxed_net        ← removed if present
└── SANDBOXED_DATA_DIR/         ← KEPT unless --data / --all
    ├── workspaces/<id>/       ← per-sandbox bind mounts
    ├── state/sandboxd.db      ← SQLite (WAL mode)
    ├── _snapshots/, templates/, library/
    └── (logs may live here or under SANDBOXED_LOG_DIR)
```

Traefik’s Docker provider is constrained to `sandboxed.managed=true`, matching the uninstall filter and ensuring preview routes only target sandboxes this stack created.

<Warning>
`--data` / `--all` deletes **all** content under `SANDBOXED_DATA_DIR`, not individual sandboxes. To drop one workspace while the stack runs, use `POST /sandbox/{id}/purge` or the v1 equivalent (see sandbox operations).
</Warning>

## Workspace retention defaults

| Action | Container | Workspace on disk | SQLite rows |
|---|---|---|---|
| Default `./uninstall.sh` | removed | **retained** | **retained** |
| `DELETE /sandbox/{id}` (API) | destroyed | retained | retained (reconcile on next boot) |
| `POST /sandbox/{id}/purge` | destroyed | deleted | purged for that id |
| `./uninstall.sh --data` | N/A (stack gone) | entire data dir deleted | deleted with data dir |

Workspaces are plain directories at `SANDBOXED_DATA_DIR/workspaces/<id>/`, bind-mounted at `/home/sandbox`. They survive idle `docker stop`, host reboot, default uninstall, and container destroy without purge.

## Day-to-day maintenance

Run these from the repository root (where `docker-compose.yml` lives).

<CodeGroup>
```bash title="Stack status"
docker compose ps
```

```bash title="Control-plane logs"
docker compose logs -f sandboxd
```

```bash title="Restart sandboxd only"
docker compose restart sandboxd
```

```bash title="Running sandbox containers"
docker ps --filter label=sandboxed.managed=true
```
</CodeGroup>

| Task | Command / signal |
|---|---|
| Rebuild control plane after Go changes | `docker compose build sandboxd && docker compose up -d sandboxd` |
| Full stack restart | `docker compose restart` or `docker compose up -d` |
| Reload API auth tokens from env | `docker compose restart sandboxd` (SIGHUP re-reads env in-process when configured) |
| Apply `.env` port/domain changes | Edit `.env`, then `docker compose up -d` |
| Liveness / readiness | `curl -s http://127.0.0.1:9090/healthz` and `/readyz` (host bind from `SANDBOXED_API_BIND`) |

<Info>
`install.sh` is idempotent: existing `.env` is left unchanged, the data directory is created if missing, base and control-plane images are built, then `docker compose up -d`.
</Info>

## Data directory layout and backup

Defaults come from `.env` / `docker-compose.yml`. `SANDBOXED_DATA_DIR` must be an **absolute** path with a **symmetric** host:container bind mount so `sandboxd` paths match host `docker run -v` paths.

| Path | Contents | Backup approach |
|---|---|---|
| `SANDBOXED_DATA_DIR/workspaces/<id>/` | User project files, agent output | Copy the directory (rsync/tar). No separate “image file” in the OSS build. |
| `SANDBOXED_DATA_DIR/state/sandboxd.db` | Sandbox lifecycle, ports, tasks metadata (SQLite WAL) | Stop `sandboxd` (or entire stack), then copy `sandboxd.db` and any `sandboxd.db-wal` / `sandboxd.db-shm` sidecars; or use `sqlite3 .backup`. |
| `SANDBOXED_DATA_DIR/state/traefik-tail.offset` | Access-log tail checkpoint | Optional; safe to omit for disaster recovery |
| `SANDBOXED_DATA_DIR/_snapshots/<id>/` | Experimental snapshot tree | Copy if you rely on snapshots |
| `SANDBOXED_DATA_DIR/templates/`, `library/` | Template/snapshot library images | Copy if used |
| `SANDBOXED_LOG_DIR/traefik-access.log` | Traefik access log (default under data dir) | Copy for audit; not required to restore sandboxes |

Override the database file location with `SANDBOXD_DB` in the `sandboxd` service environment (default: `{SANDBOXED_DATA_DIR}/state/sandboxd.db`).

<Steps>
<Step title="Quiesce writes">
`docker compose stop sandboxd` (or `docker compose down` for a full pause).
</Step>
<Step title="Copy workspaces">
Archive each `workspaces/<id>/` you need, or the whole `workspaces/` tree.
</Step>
<Step title="Copy SQLite state">
Archive `state/sandboxd.db` and WAL sidecars together, or run a SQLite backup against the DSN path.
</Step>
<Step title="Start the stack">
`docker compose up -d`. On boot, the reconciler converges Docker containers to SQLite (SQLite is source of truth).
</Step>
</Steps>

<Tip>
For a running system without stopping the stack, prefer per-sandbox workspace copies over copying `sandboxd.db` while `sandboxd` is writing. ARCHITECTURE documents workspace backup as copying the directory; state backup as copying the DB file when quiesced.
</Tip>

## Managed-container cleanup without full uninstall

To clear sandboxes but leave the control plane up:

```bash
docker ps -aq --filter label=sandboxed.managed=true | xargs -r docker rm -f
```

Use the API for selective lifecycle: `POST /v1/sandboxes/{id}/stop` frees RAM but keeps the workspace; `DELETE /sandbox/{id}` destroys the container and keeps files; `POST /sandbox/{id}/purge` removes both.

## Uninstall vs install responsibilities

| Component | `install.sh` | Default `uninstall.sh` | `uninstall.sh --all` |
|---|---|---|---|
| `.env` | create from example if missing | untouched | untouched |
| `SANDBOXED_DATA_DIR` | `mkdir -p` | preserved | removed |
| Base + control-plane images | build | preserved | removed (with `--images`) |
| Compose services | `up -d` | `down` | `down` |

## Related pages

<CardGroup>
<Card title="Installation" href="/installation">
Prerequisites, `./install.sh`, `.env` bootstrap, and health checks before you need uninstall.
</Card>
<Card title="Workspaces and persistence" href="/workspaces-persistence">
Directory layout, bind mounts, isolation, and what survives stop/destroy.
</Card>
<Card title="Sandbox lifecycle" href="/sandbox-lifecycle">
SQLite status machine, destroy vs purge, and reconcile-on-boot after maintenance.
</Card>
<Card title="Observability" href="/observability">
`healthz`/`readyz`, metrics, and log paths including Traefik access logs.
</Card>
<Card title="Configuration reference" href="/configuration-reference">
`SANDBOXED_DATA_DIR`, `SANDBOXED_LOG_DIR`, network, and image env keys.
</Card>
<Card title="Troubleshooting" href="/troubleshooting">
Compose log probes, Docker socket failures, and port conflicts when restarting the stack.
</Card>
</CardGroup>
