Deployment model
How deeplo deploys — the full sequence from commit detection to runtime verification.
Agentless SSH
No software runs permanently on target hosts. The daemon SSHes in at deploy time, uploads files, and runs docker compose commands. Between deploys, hosts are untouched.
From commit to deploy
When a new commit is detected on a tracked branch:
1. Plan — the planner checks every configured project whose repo matches the event. For each, it tests whether any changed file matches the project's watch_paths. Projects with no match are skipped. Projects with empty watch_paths always match.
2. Dedup — if the same (project, host, commit SHA) is already running or was successfully deployed, it's skipped. In poll mode, a branch tip SHA is dispatched once, so a failed deploy of that same SHA is not retried automatically on the next poll cycle.
3. Fetch — the daemon clones or fetches the repository into a local mirror cache under {DEEPLO_DATA_DIR}/repos/. Subsequent deploys of the same repo reuse the mirror; only new objects are fetched.
4. Extract — the compose files and env files for the project are read directly from the git object database at the exact commit SHA. No working tree is needed.
5. Upload — files are uploaded to a temporary staging directory on the target host (e.g., {remote_dir}.staging/) over SFTP.
6. Preflight — docker compose config is run in the staging directory on the remote host. This validates the compose files without touching any running containers. If it fails, the staging directory is cleaned up and the live directory is left untouched.
7. Swap — the live directory is replaced with the staging directory using a sequence of mv commands:
mv {remote_dir} {remote_dir}.oldmv {remote_dir}.staging {remote_dir}rm -rf {remote_dir}.old
On Linux, mv is atomic for a single directory. This sequence ensures that once the new directory is in place, it is complete. There is a tiny window between steps 1 and 2 where the project directory does not exist.
8. Up — docker compose up --detach --remove-orphans --quiet-pull is run in the remote project directory.
9. Verify — docker compose ps is run immediately after. If any service is not in the running state (e.g. exited or restarting), the deploy is reported as failed. No containers are stopped or rolled back.
10. State — deployment result (success, failure, or runtime verification failure) is written to local state files.
Remote directory layout
Each project gets a stable directory on the target host. Temporary directories are created alongside it during deployment:
{remote_base_dir}/
{remote_dir_name}/ ← current live project directory
{remote_dir_name}.staging/ ← temporary; created during upload
{remote_dir_name}.old/ ← temporary; used during the swap stepThe remote directory name defaults to the project name. Override with remote_dir_name in the project config.
Compose project identity is derived from the deeplo project name, not from remote_dir_name. Changing remote_dir_name moves the files on disk but keeps the same Compose project identity, which avoids container-name conflicts caused purely by renaming the deployment directory.
Preflight validation
Before any live files are touched, the daemon runs docker compose config in the staging directory on the remote host. This checks:
- YAML syntax
- Variable substitution errors
- Schema validity
If preflight fails, the deploy is aborted cleanly. The live directory is not modified. The error is logged and written to state.
Runtime verification
After docker compose up succeeds, deeplo runs docker compose ps and checks the state of every service. If any service reports a state other than running (e.g. exited, restarting, dead), the deploy is recorded as failed.
This catches the common case of a container that starts and immediately exits — for example due to a bad config, a missing file, or a crash on startup.
Limits:
- The check happens once, immediately after
up. It does not wait for health checks to complete. - It does not detect containers that start successfully and crash later.
- It does not perform any application-level readiness checks (HTTP, TCP, etc.).
- No automatic rollback is performed. The files on disk reflect the new deploy regardless of container state.
No rollback
There is no rollback command. The practical way to recover from a bad deploy is to fix the issue and push a new commit, or revert the bad commit and push — this triggers a normal deploy with the corrected files.
Multi-host deploys
When a project targets multiple hosts, all (project, host) pairs are dispatched in parallel to the runner's worker pool. A failure on one host does not affect the others.
The runner enforces concurrency limits:
- A global worker limit (default: number of CPU cores)
- A per-host limit (default: 1 concurrent deploy per host)
These prevent overloading a host with simultaneous deploys.
Refresh
deeplo refresh connects to all hosts, runs docker compose ps for each project, and prints the observed runtime state.
Use it when:
- The daemon was offline when deploys happened on some hosts
- The data directory was moved or lost
- You want to sync state after manual
docker composeoperations on a host
deeplo refresh
PROJECT HOST SERVICE STATE STATUS
------- ---- ------- ----- ------
myapp web-1 web running Up 2 hours
myapp web-2 web running Up 2 hoursIt does not modify deployment history or synthesize a fake deploy record from a running container.