
Infrastructure as plain Guile programs.
A deployment is a Scheme script. Each resource call converges the real thing, a server, a DNS record, a whole Guix System, and hands back its outputs. No CLI, no plan to apply, no daemon.
(use-modules (chaudron)
(chaudron hetzner)
(chaudron cloudflare))
(with-stage "prod"
(define forge
(hetzner-server "forge" #:type "cx23"
#:location "fsn1"))
(cloudflare-dns-record "git"
#:zone "example.com"
#:content (output forge 'ipv4)))chaudron: create hetzner-server/forge
chaudron: create cloudflare-dns-record/git
One language, from the cloud to the system
Infrastructure usually takes two tools: one for the servers and the DNS, another, with its own language, to configure the machines. Here both are Scheme, in the same program.
The machines are declared too
With Guix System, a server is not configured step by step: it is described whole. Deploying the same description gives the same system, and each deployment can be rolled back.
Procedures, not templates
A website, its server, its DNS record and its system are one procedure. Two websites are two calls, with loops and modules for the rest.
Code is data
The system of a machine is a Scheme expression: the program builds it, with its values in place, rather than filling text templates.
Everything pinned
The versions of Guix, of chaudron and of every package on the servers are fixed by the project's lock: the same code, the same result.
(define (website name key)
(let* ((domain (string-append name ".example.com"))
(server (hetzner-server name #:type "cx23"
#:location "fsn1"
#:ssh-keys (list key))))
(cloudflare-dns-record name #:zone "example.com"
#:content (output server 'ipv4))
(hetzner-guix-system name
#:server server
#:os (write-system! name domain)
#:ssh-key key)))
(website "blog" key)
(website "shop" key)(define (system-of domain)
`(operating-system
(inherit %base)
(services
(cons* (service nginx-service-type
(nginx-configuration
(server-blocks
(list (nginx-server-configuration
(server-name (list ,domain))
(listen '("443 ssl")))))))
(service certbot-service-type
(certbot-configuration
(certificates
(list (certificate-configuration
(domains (list ,domain)))))))
(operating-system-user-services %base)))))What a run does
Running the script is the deployment. A stage is one environment, like "prod", and its body is ordinary Guile: loops, procedures and modules all work.
Load the stage
with-stagereads what chaudron knows about the stage from.chaudron/prod/: one small file per resource.Converge each call, in order
Each resource call looks at the real resource, brings it back if it drifted, then creates, updates or replaces it, and saves its state right away. Wrap independent calls in
concurrentlyto run them in parallel.Delete what is gone
When the body returns, resources the script no longer declares are deleted, each before what it depends on. If the body raised an error, nothing is deleted.
Nothing hidden
Everything chaudron knows and does is plain text you can read, diff and commit.
The state is data
One s-expression per resource. It is read, never evaluated.
((kind . hetzner-volume)
(name . "data")
(seq . 4)
(props (name . "data-prod")
(size . 10)
(location . "fsn1")
(format . "ext4")
(labels)
(protected? . #f))
(outputs
(id . 104729301)
(linux-device . "/dev/disk/by-id/scsi-0HC_Volume_104729301"))
(depends-on))Dry runs say what would change
The same script with #:dry-run? #t reads the real resources and changes nothing.
chaudron: create hetzner-volume/data (dry run)
chaudron: update hetzner-server/forge (volumes) (dry run)
chaudron: delete hetzner-firewall/web (dry run)
Drift is reported, then fixed
A change made by hand in the console is undone by the next run, out loud.
chaudron: hetzner-server/forge has drifted: type is "cx33", expected "cx23"
chaudron: update hetzner-server/forge (type)
From a bare server to guix deploy
Give hetzner-guix-system a server and an operating-system file. The first run installs Guix System on it; later runs deploy again whenever the file, or the Guix you run, changes.
- Boots the server into Hetzner's rescue system.
- Erases its disk and installs a minimal Guix System.
- Deploys your configuration with
guix deploy.
(hetzner-guix-system "forge"
#:server forge
#:os "forge-os.scm"
#:ssh-key key)Providers
- Hetzner Cloud
hetzner-serverhetzner-ssh-keyhetzner-firewallhetzner-primary-iphetzner-volumehetzner-networkhetzner-load-balancerhetzner-snapshot
- Guix System
hetzner-guix-system
- Cloudflare
cloudflare-dns-recordcloudflare-workercloudflare-kv-namespacecloudflare-d1-database
- Livebox experimental
livebox-port-forwardlivebox-dhcp-lease
Write your own
A provider is a module with four procedures, of which only create and delete are required. Props and outputs are association lists. Without update, any change replaces the resource.
(define thing-provider
(register-provider!
(make-provider 'acme-thing
#:create (lambda (name props) ...)
#:read (lambda (outputs) ...)
#:update (lambda (outputs old new) ...)
#:delete (lambda (outputs) ...)
#:replace-on '(region))))Get started
With Guix, a project pins chaudron in its own channels.scm and runs it with guix time-machine: nothing to install. On Debian and Ubuntu, apt has all it needs.
Installation ยท Your first deployment
chaudron is young: its interface may still change.
$ guix time-machine -C channels.scm -- \
describe -f channels > channels.lock.scm
$ guix time-machine -C channels.lock.scm -- \
shell guile chaudron
$ guile deploy.scm