| If the storage provider is | Read this |
|---|---|
Local (the default) | Local storage |
S3-compatible | S3 storage |
Storage section of ekso.json — see the Configuration reference. Most self-host installs are Local; cloud-platform installs (Render, Fly, AWS) typically run S3.
The two sections at the bottom — docker compose down -v reassurance and off-machine backups — apply to both backends.
Local storage
The default. Both theapp and worker containers read and write the same host-mounted folder. The bundle ships a backup service that takes daily snapshots automatically, so a single mistake never costs you data.
Where your data lives
The folders below sit next to yourdocker-compose.yml — wherever you unzipped the bundle.
Quickstart bundle
| Folder | What’s in it | Whose responsibility |
|---|---|---|
./data/postgres/ | PostgreSQL database files — tickets, projects, time logs, users, every record Ekso holds | The bundle (auto-snapshotted, see below) |
./data/storage/ | Uploaded attachments — files, images, signatures | The bundle (auto-snapshotted) |
./backups/ | Compressed snapshots of the two folders above | The bundle (rolled forward daily) |
BYOD bundle
| Folder | What’s in it | Whose responsibility |
|---|---|---|
./data/storage/ | Uploaded attachments | The bundle (auto-snapshotted) |
./backups/ | Compressed snapshots of ./data/storage/ | The bundle |
| Your external database | Tickets, projects, time logs, users | You — back up with your existing DB tooling |
docker compose down -v and docker volume prune cannot delete these folders. They live outside Docker’s storage area entirely. The only way to delete data is an OS-level command — rm -rf ./data on macOS/Linux, or Remove-Item -Recurse -Force ./data in PowerShell. That is deliberate, and it is the only path. See What happens with docker compose down -v below.How automatic backups work
Each bundle includes a smallbackup service that takes a snapshot every 24 hours. The first snapshot lands about 30 seconds after the stack starts, so you have a backup from day one.
| Property | Value |
|---|---|
| Schedule | Every 24 hours from container start |
| Retention | 14 days (older snapshots auto-pruned) |
| Location | ./backups/db/ and ./backups/storage/ (Quickstart); ./backups/storage/ only (BYOD) |
| Format | .sql.gz for the database; .tar.gz for storage |
[backup] … done; sleeping 24h lines in the output.
Take a snapshot on demand
The backup loop runs a snapshot first thing when the container starts. To produce a fresh snapshot immediately — typically before a risky upgrade or schema change — restart the backup container:./backups/db/ and ./backups/storage/ (or just ./backups/storage/ for BYOD) within a few seconds.
Customize retention or disable backups
Retention and schedule are configured indocker-compose.yml rather than ekso.json — they’re properties of the backup service, not the application. To change retention from 14 days to 30:
backup: service block in docker-compose.yml and run docker compose up -d.
Restoring from a snapshot
You shouldn’t normally need this —docker compose pull && docker compose up -d upgrades preserve data automatically — but when you do, the commands below are the manual sequence. Run from the folder containing docker-compose.yml.
Quickstart — restore the database
restore.sh (macOS / Linux) and restore.ps1 (Windows) that wrap this sequence. From the bundle directory:
-y (bash) or -Yes (PowerShell) to skip the prompt.
Restore uploaded files
restore.sh and restore.ps1 accept an optional second argument for the storage snapshot:
BYOD — database restore
Your database is outside Docker; nothing in the Ekso bundle touches it. Restore from your own database backup tooling —pg_restore for Postgres, RESTORE DATABASE for SQL Server, your DBaaS provider’s snapshot console, whatever you used to take the backup. Once the database is restored, docker compose start api worker brings the application back online against it.
S3 storage
When the storage provider is set to S3-compatible (Settings → System → Storage), attachments live in your bucket instead of on the host filesystem. Theapp and worker containers each talk to the bucket directly — they no longer share a folder, which is what makes this configuration work on cloud platforms like Render or Fly where containers can’t share a persistent disk.
Where your data lives
| Asset | Where it is | Whose responsibility |
|---|---|---|
| Database | ./data/postgres/ (Quickstart) or your external server (BYOD) | The bundle (Quickstart, auto-snapshotted) or you (BYOD) |
| Uploaded attachments | Your S3-compatible bucket, under keys <tenantId>/<fileId> | Your bucket provider’s durability + your lifecycle rules — see Attachment durability below |
| Database backups | ./backups/db/ (Quickstart only — bundle still snapshots the local DB) | The bundle |
./data/storage/ | Empty / unused — Ekso never writes to it under S3 | n/a |
ekso.json and doesn’t manage the bucket itself — lifecycle, versioning, and access policies are configured in your provider’s console.
Database backups
The bundledbackup service still runs and still snapshots the database every 24 hours into ./backups/db/ for Quickstart installs. The storage tarball it produces in ./backups/storage/ will be empty (Ekso writes no files there), and you can ignore those empty tarballs or comment out the tar line in the backup service to skip them entirely.
For BYOD installs, the bundled backup service has nothing to do — the database is external and storage is in the bucket. Comment out the entire backup: service block in docker-compose.yml if you want a tidier compose surface.
How automatic backups work, taking a snapshot on demand, and customising retention all work identically to the Local section above — the only difference is what gets snapshotted.
Attachment durability
Object stores already give you 11 nines of durability without you doing anything. What you should configure once, in your provider’s console:- Versioning — turn it on. With versioning, a deleted or overwritten object is recoverable for as long as you keep its prior version. This is the equivalent of the bundle’s 14-day storage snapshots, but at object granularity.
- Lifecycle rule — auto-expire non-current versions after the retention window you want (e.g. 30 or 90 days). Without a rule, versions accumulate forever and your bill grows.
- Off-region replication (optional) — for disaster recovery, replicate the bucket to a second region or a second provider. See off-machine backups below.
| Provider | Versioning | Lifecycle |
|---|---|---|
| AWS S3 | Bucket → Properties → Bucket Versioning → Enable | Bucket → Management → Lifecycle rules |
| Cloudflare R2 | Bucket → Settings → Object versioning | Bucket → Settings → Object lifecycle rules |
| Backblaze B2 | Bucket → Lifecycle Settings (versioning is implicit per setting) | Same screen |
| MinIO | mc version enable then mc ilm import | mc ilm commands |
Restoring attachments
How you restore depends on what failed and which durability layer you’ve turned on. Single-file recovery (versioning enabled): restore the prior version from the provider console. AWS S3, R2, and B2 all expose a “Show versions” toggle in the bucket browser; select the version you want and copy/promote it to current. No Ekso involvement needed — when anapp request reads the key, the bucket serves the current version.
Whole-tenant recovery (versioning enabled): for AWS S3, the aws s3api list-object-versions + bulk-restore pattern works under the <tenantId>/ prefix. R2 and B2 expose equivalent CLI flows.
Bucket-level loss (versioning not enabled, or accidental bucket deletion): restore from your off-bucket replica or backup if you set one up. If you didn’t, attachments are gone — which is why versioning + lifecycle is the load-bearing default for S3 installs. Database restore for Quickstart still works from ./backups/db/ using the Quickstart restore commands above.
Database restore: identical to the Local section — see Quickstart — restore the database (bundled Postgres) or BYOD — database restore (external DB). The bucket isn’t involved in DB restore.
What happens with docker compose down -v
A common worry, especially for shops with strict ops policies, is that docker compose down -v will silently wipe data. With the layout described above, it doesn’t.
| Command | Effect on ./data/ (Local) | Effect on ./backups/ | Effect on your S3 bucket |
|---|---|---|---|
docker compose down | Untouched | Untouched | Untouched |
docker compose down -v | Untouched | Untouched | Untouched |
docker volume prune | Untouched | Untouched | Untouched |
docker system prune -a --volumes | Untouched | Untouched | Untouched |
| Docker Desktop “Reset to factory defaults” | Untouched | Untouched | Untouched |
rm -rf ./data (or PowerShell equivalent) | Deleted | Untouched | Untouched |
-v to delete. Local data is destroyed only by an explicit OS-level delete of the folder, which is observable, intentional, and recoverable from ./backups/ for 14 days. S3 data lives entirely outside Docker — no Docker command of any kind can touch the bucket.
Off-machine backups
The bundledbackup service writes to ./backups/ on the same machine running Ekso. That protects you from Docker mistakes, application bugs, and most operational issues — but not from disk failure, ransomware, or a lost laptop.
For real disaster recovery, copy ./backups/ somewhere off the machine on a schedule that fits your business — an external drive, a NAS, S3, Backblaze B2, OneDrive, Google Drive, your existing backup tool’s network share. Anywhere off this machine.
“S3 as an off-machine backup destination” (this section) is a different concept from “S3 as Ekso’s storage backend” (above). You can use one, both, or neither. A common pattern: Ekso stores attachments in an R2 bucket (storage provider set to S3-compatible in Settings), and a nightly job replicates
./backups/db/ to a second R2 bucket in another region for DR.rsync, rclone, or your tool of choice.
For BYOD installs, your database backups (which live wherever your DB tooling puts them) need the same off-machine treatment. Don’t assume the bundle’s storage snapshots are the whole picture — your DB is the bigger risk surface.
For S3-storage installs, the bucket itself usually carries provider-side replication options (S3 Cross-Region Replication, R2’s bucket-to-bucket sync via wrangler, B2’s replication rules). Configure one of those if a single-region bucket failure is in your threat model.
See also
- Configuration reference → Storage — every field in the
Storagesection ofekso.json - Upgrading — release cadence, version pinning, and the rollback procedure that uses the restore commands above
- Docker - full stack and Docker - bring your own database — the install walkthroughs