State files
What deeplo stores on disk, where it stores it, and how to recover from common data loss scenarios.
All state is stored as plain JSON files under DEEPLO_DATA_DIR. No database is required. You can inspect, copy, and restore state files manually.
Directory layout
{DEEPLO_DATA_DIR}/
state/
projects.json latest deployment per (project, host) pair
poll/
<repo>-<branch>.json poller state per (repo, branch) pair
history/
runs/
<id>.json one file per deployment attempt (JSON)
runs/
<id>.log plain-text run log per deployment attempt
repos/
<slug>.git/ local mirror clone of each watched repostate/projects.json
A JSON object mapping "project/host" keys to the most recent Deployment record for that pair. Updated on every deploy attempt.
Used by deeplo status and by the deploy deduplication check.
{
"myapp/web-1": {
"id": "1744644600-a3f2bc91",
"project": "myapp",
"host": "web-1",
"commit_sha": "aabbccddee...",
"branch": "main",
"status": "success",
"trigger_source": "webhook",
"started_at": "2026-04-14T15:30:00Z",
"completed_at": "2026-04-14T15:30:05Z"
}
}history/runs/<id>.json
One file per deployment attempt. The ID format is <unix-seconds>-<4-random-bytes-hex>, for example 1744644600-a3f2bc91.
Contains the full Deployment record including error messages for failed deploys. These JSON records accumulate indefinitely — there is no automatic cleanup.
runs/<id>.log
One plain-text log file per deployment attempt, written at deploy time. Served by the log server when DEEPLO_LOGS_PORT is set (see GitHub reporting).
Retention is controlled by DEEPLO_LOG_RETENTION_DAYS (default: 14 days). Set to 0 to keep log files forever.
state/poll/<repo>-<branch>.json
Poller state for a (repo, branch) pair. Written on each poll cycle.
{
"repo": "infra",
"branch": "main",
"last_seen_sha": "aabbccdd...",
"last_deployed_sha": "aabbccdd...",
"last_polled_at": "2026-04-14T16:00:00Z",
"trigger_source": "poll",
"updated_at": "2026-04-14T16:00:00Z"
}The last_deployed_sha field records the most recent branch tip SHA dispatched by the poller. It is written before the deploy runs. This prevents double-dispatch if the daemon restarts mid-deploy or if a poll and a webhook race for the same commit, but it also means poll mode does not automatically retry a failed deploy of the same SHA on the next poll cycle.
repos/<slug>.git/
Local bare mirror clones of each watched repository, created by git clone --mirror. The slug is derived from the repo URL with non-alphanumeric characters replaced by _.
Used for:
- Reading compose/env files at exact commit SHAs without a working tree
- Computing file diffs between commits for
watch_pathsmatching
These directories can be safely deleted. They will be re-cloned on the next deploy that needs them (the first deploy after deletion will be slightly slower due to the full clone).
Recoverability
Scenario: data directory lost or moved
If you lose state/projects.json, deeplo status shows no deployments. The running containers on your hosts are unaffected. deeplo refresh can still inspect live runtime state, but it does not rebuild deployment history.
Scenario: daemon restarted mid-deploy
A deploy in progress when the daemon restarts will have status: running in state but may or may not have completed. Run deeplo refresh to inspect the actual container state; it does not overwrite the recorded deploy entry.
Scenario: poll state lost
If state/poll/ is lost, the poller treats the next detected commit as new and redeploys. This is safe — it's a redeploy of an already-running commit, which the dedup check handles:
- If
projects.jsonhas a success record for that SHA → skip (dedup) - If
projects.jsonis also gone → deploy runs (harmless, containers restart with the same config)
Scenario: git mirror lost
The mirror is recreated on the next deploy. No history is lost.
What is not stored
- Container logs — these live on the target host
- Image digests or versions — the daemon doesn't interact with registries
- Environment variable values — only file paths are stored; files are read at deploy time
- Webhook delivery history (beyond what the running/success dedup check needs)