deeplo
Reference

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 repo

state/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_paths matching

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.json has a success record for that SHA → skip (dedup)
  • If projects.json is 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)

On this page