1---
2title: Servers
3description: Launch Hetzner Cloud Servers with Alchemy — SSH keys and typed SSH access, cloud-init, networks, volumes, snapshots, and placement groups.
4---
5
6A [`Server`](/providers/hetzner/server) is a Hetzner Cloud VM: pick
7a size, an OS image, and a location, and it boots in about ten
8seconds, billed by the hour. It's both a raw compute primitive and
9the host that [Services](/hetzner/compute/services) deploy onto.
10
11## Launch a server
12
13The minimal server is a type, an image, and a location:
14
15```typescript
16const server = yield* Hetzner.Server("box", {
17 serverType: "cx22",
18 image: "ubuntu-24.04",
19 location: "nbg1",
20});
21```
22
23All three are create-only — changing any of them **replaces** the
24server. The resolved resource exposes `ipv4`, `ipv6`, `serverId`,
25`status`, and the rest of the observed state — see the
26[`Server` reference](/providers/hetzner/server).
27
28To discover what's available, use the catalog helpers — each
29accepts a name or numeric id and fails with a typed
30`CatalogNotFound` if it doesn't exist:
31
32```typescript
33const serverType = yield* Hetzner.findServerType("cx22");
34const image = yield* Hetzner.findImage("ubuntu-24.04");
35const location = yield* Hetzner.findLocation("nbg1");
36```
37
38## SSH keys
39
40Register your public key as an [`SshKey`](/providers/hetzner/sshkey)
41and inject it at create time:
42
43```typescript
44const key = yield* Hetzner.SshKey("laptop", {
45 publicKey: "ssh-ed25519 AAAA... you@laptop",
46});
47
48const server = yield* Hetzner.Server("box", {
49 serverType: "cx22",
50 image: "ubuntu-24.04",
51 sshKeys: [key],
52});
53```
54
55Hetzner applies injected keys only when the server is provisioned —
56changing `sshKeys` later doesn't touch a live server.
57
58Separately from your keys, Alchemy generates a **deploy key** for
59every server it creates and injects it at boot. That key (persisted
60redacted in state) is how [`Hetzner.Ssh`](#run-commands-over-ssh)
61and [`Hetzner.Service`](/hetzner/compute/services) reach the box
62with zero SSH configuration on your side.
63
64## Run commands over SSH
65
66The `Ssh` binding gives you typed `exec`/`scp` against any managed
67server, from deploy-time Effects like
68[Actions](/infrastructure-as-code/action):
69
70```typescript
71const ssh = yield* Hetzner.Ssh(server);
72
73const { stdout } = yield* ssh.exec("uname -a");
74yield* ssh.scp("./assets.tar.gz", "/opt/app/assets.tar.gz");
75```
76
77`exec` returns `{ stdout, stderr, code }` and **fails** with a typed
78`Hetzner.SshError` on a non-zero exit. `scp` accepts a local path or
79a `Uint8Array` and creates the remote directory if needed. Both use
80the server's deploy key and the `root` user by default; pass
81`{ user, privateKey }` to override.
82
83## Cloud-init
84
85`userData` runs on the server's first boot — the place to install
86packages, write config files, or add users:
87
88```typescript
89const server = yield* Hetzner.Server("box", {
90 serverType: "cx22",
91 image: "ubuntu-24.04",
92 userData: `#cloud-config
93packages:
94 - postgresql
95`,
96});
97```
98
99A shell script works too, and so does a bare snippet — Alchemy adds the
100`#!/bin/bash` shebang cloud-init needs to execute one:
101
102```typescript
103const server = yield* Hetzner.Server("box", {
104 serverType: "cx22",
105 image: "ubuntu-24.04",
106 userData: `#!/bin/bash
107apt-get update
108DEBIAN_FRONTEND=noninteractive apt-get install -y nginx
109systemctl enable --now nginx
110`,
111});
112```
113
114Alchemy has its own bootstrap script that pre-installs
115[Bun](https://bun.sh) for [Services](/hetzner/compute/services). Your
116`userData` does not replace it: the two are combined into a multipart
117cloud-init document and both run, Alchemy's first. To take over the
118whole payload, bootstrap included, pass a document that already starts
119with a `Content-Type:` header.
120
121The composed document is capped at 32 KiB (Hetzner's limit) — over that,
122the deploy fails with `Hetzner.ServerUserDataTooLarge`.
123
124:::caution
125Cloud-init only runs once, on first boot, so **changing `userData`
126replaces the server**. Plan for it the way you would an `image` or
127`serverType` change: the new server is created, dependents are pointed
128at it, then the old one is deleted.
129:::
130
131## Attach infrastructure
132
133Networks, firewalls, volumes, and a placement group are all props —
134each accepts the resource directly (yielded or module-scope) and is
135kept in sync on update:
136
137```typescript
138const server = yield* Hetzner.Server("box", {
139 serverType: "cx22",
140 image: "ubuntu-24.04",
141 networks: [network],
142 firewalls: [firewall],
143 volumes: [volume],
144 placementGroup: group,
145});
146```
147
148See [Networking](/hetzner/networking) for networks, firewalls, and
149load balancers, and [Volumes](/hetzner/data/volumes) for storage.
150
151## Placement groups
152
153A [`PlacementGroup`](/providers/hetzner/placementgroup) of type
154`spread` guarantees its members run on distinct physical hosts —
155cheap insurance for redundant pairs:
156
157```typescript
158const spread = yield* Hetzner.PlacementGroup("spread", {
159 type: "spread",
160});
161
162const a = yield* Hetzner.Server("a", { /* ... */ placementGroup: spread });
163const b = yield* Hetzner.Server("b", { /* ... */ placementGroup: spread });
164```
165
166## Snapshots
167
168[`Image`](/providers/hetzner/image) captures a server's disk as a
169snapshot (or converts an existing backup):
170
171```typescript
172const golden = yield* Hetzner.Image("golden", {
173 server,
174 type: "snapshot",
175 description: "app image v1",
176});
177```
178
179Boot new servers from it by passing the image to another Server's
180`image` prop.
181
182## Where next
183
184- [Services](/hetzner/compute/services) — deploy code to the server
185 as a supervised systemd unit.
186- [Volumes](/hetzner/data/volumes) — durable block storage.
187- [Networking](/hetzner/networking) — private networks, firewalls,
188 load balancers, stable IPs.
189- [`Server` reference](/providers/hetzner/server) — every prop and
190 attribute.