Most apps do not need a volume. Use
/shared.db and /shared/ for state shared across
instances and regions.
A persistent volume is a private filesystem for software that expects local disk, such as Postgres, Redis, or a development environment. Attaching one makes the service stable: its identity and disk remain together across deploys, restarts, and host moves.
A workspace uses its persistent volume for the complete writable filesystem.
Package installs and changes outside /data survive workspace restarts and
host moves. /data remains the conventional project path. See coding agents
& workspaces for the workspace workflow.
attach a volume
Attach a volume to an automatic source build:
kedge up --volume /var/lib/myapp --volume-size 20GiB
For a prebuilt image:
kedge publish --app my-database --image postgres:18 \
--volume /var/lib/postgresql/data \
--volume-size 20GiB
size is a sparse logical limit. The volume is seeded from the image path on
first boot.
Dockerfiles and images
A Dockerfile VOLUME creates a one-machine app by default:
VOLUME ["/var/lib/postgresql/data"]
Use --machines 0 when an image declares a volume the service does not need:
kedge publish --app myapp --image example/myapp:1.2 --machines 0
Several volume paths for one machine share its underlying store while remaining
separate directories. Dockerfile and image volume declarations seed those
paths unless volume.nocopy is set.
Compose
Use a named volume for a service in a Compose app:
services:
db:
image: postgres:18
volumes: [data:/var/lib/postgresql/data]
volumes:
data:
x-kedge:
shared: false
size: 20GiB
shared: false gives each machine its own persistent volume. A
shared: true volume mounts
the app's replicated /shared/ tree instead.
With a persistent volume, deploy.replicas sets the machine count. Outside
Compose, use --machines:
kedge up --volume /data --machines 3
machines
A service with a persistent volume runs on machines. Machines have ordinals starting at zero:
<ordinal>.<service>.<app>.internalreaches one machine.<service>.<app>.internalreaches the whole set.
Each machine has its own volume. Scaling down retains that volume; scaling up reattaches the same ordinal.
A set runs one machine at minimum. Machines do not scale to zero, because a
machine keeps its address and volume for its lifetime. min: 0 and
scale.idle-cooldown apply only to pooled instances, which
have no persistent volume.
idle-suspend makes an idle machine cheap without removing it. Set a delay and
a machine with no inbound traffic pauses in place, keeping its address, volume,
and host; the next packet restarts it. A paused machine bills no CPU and no
memory.
services:
db:
x-kedge:
machines: 3
idle-suspend: 10m
Delays under 2s are raised to 2s.
A machine is paused on inbound traffic and guest CPU alone, so a request that goes quiet on both while it is still being served can be paused mid-flight. It resumes within seconds and the request completes slowly rather than hanging. To rule it out, hold a shared lock while you serve:
flock -s /run/kedge/awake ./handle-request
Shared locks do not serialize, so concurrent requests each hold one.
A paused machine wakes on traffic, so declare it only where traffic is what the machine waits for. Nothing wakes one whose next action is its own timer. Kedge wakes a paused machine periodically anyway so scheduled work still runs, and steps its clock forward on each wake.
The same lock, taken exclusively, holds a machine awake through work that must not be interrupted:
flock /run/kedge/awake ./nightly-compaction.sh
The machine stays up while the lock is held. The kernel releases it when the process exits, so a job that crashes does not leave a machine awake forever.
List machines or move one to another region:
kedge machines my-database
kedge machines my-database migrate 0 nrt
Kedge handles ordinary restarts and host recovery automatically.
durability
Volume flushes use the host's local disk (NVMe on production hosts). fsync,
ext4 journal commits, and database checkpoints do not wait for object storage.
Kedge publishes local writes to object storage in the background after 1s or 16 MiB. A controlled stop, machine migration, or fork waits until every accepted write is published before it proceeds. If object storage is unavailable, that operation fails and leaves the machine in place.
The local writeback limit is 4 GiB per volume by default. If publication cannot keep up and the limit fills, new writes fail instead of consuming unbounded host memory. Restarting the daemon on the same host replays the local journal. After complete host loss, recovery uses the last published remote state.
Check publication state:
kedge volumes status my-database-0
clean means every write accepted by the volume backend is present in the
remote manifest. It does not include data still held in the guest filesystem's
page cache. Other states report pending writes, object-store errors, lease
fencing, or a full local journal.
retention and deletion
Machine volumes are named <app>-<ordinal> and never disappear as a side
effect. App deletion, service removal, or scale-down leaves them retained.
List and explicitly delete retained volumes:
kedge volumes
kedge volumes rm my-database-0
Attached volumes and ancestors still used by forks refuse deletion. The generated REST volume operations expose the same operations.