Skip to content

Volumes

A Volume is Hetzner’s network block storage: a durable disk (10 GB minimum) with its own lifecycle, attachable to one Server at a time in the same location. Servers come and go; Volumes keep the data.

import * as Hetzner from "alchemy/Hetzner";
export const Data = Hetzner.Volume("data", {
size: 10,
format: "ext4",
location: "nbg1",
});

size is in GB and can grow in place on a later deploy — shrinking is impossible. format (ext4 or xfs) and location are create-only: changing either replaces the Volume and destroys its data. The location must match the Server’s.

The MountVolume binding is the highest-level path: inside a Service’s init, bind the Volume to a path and use it as a plain directory at runtime. Pass the Data declaration directly — no yielding required:

export default class Api extends Hetzner.Service<Api>()(
"Api",
{ server: Box, main: import.meta.url, port: 3000 },
Effect.gen(function* () {
const mount = yield* Hetzner.MountVolume(Data, { path: "/data" });
return {
fetch: Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const text = yield* fs.readFileString(`${mount.path}/hello.txt`);
// ...
}),
};
}).pipe(Effect.provide(Hetzner.MountVolumeLive)),
) {}

At deploy time the binding attaches the Volume to the Service’s Server, creates the directory, mounts the device, and appends an /etc/fstab entry (defaults,nofail) so the mount survives reboots. At runtime it hands you the resolved mount.path and mount.device. Every step is idempotent, and two Services binding the same (volume, path) on the same Server share one attach and one mount.

If the Server is ever replaced, the Volume is detached from the old machine and re-attached to the new one on the next deploy — the data rides along.

For servers that manage their own filesystems, attach declaratively instead. Either from the Volume side:

export const Data = Hetzner.Volume("data", {
size: 10,
location: "nbg1",
server: Box,
automount: true,
});

Or as a standalone VolumeAttachment — useful when the Volume and Server are declared far apart:

yield* Hetzner.VolumeAttachment("data-on-box", {
volume: Data,
server: Box,
automount: true,
});

With automount: true Hetzner mounts the disk under /mnt/HC_Volume_<id>; otherwise mount volume.linuxDevice yourself (e.g. via cloud-init or Hetzner.Ssh).