I finally described my cloud infrastructure in a declarative configuration. It’s
a modest setup: a DNS zone, this blog, and a couple of VM instances. Terraform,
which I use at work, was my initial choice. I quickly imported the existing
resources, but the resulting code didn’t spark joy. It felt too verbose and
lacked expressiveness. HCL still relies on for_each
and count
for even basic
flow control.
Looking for alternatives, I chose Pulumi because it supports configuration in general-purpose programming languages like Typescript, Go, or Java. Lisp dialects weren’t supported, so I settled on Typescript. Emacs with LSP support provided a great developer experience, offering contextual autocomplete and easy access to comprehensive documentation.
For secrets management, Pulumi offers ESC, which requires account registration
and is dependent on their cloud. Since I don’t need RBAC or anything complex, I
replaced it with SOPS. A small helper script (direnv/lib/sops.sh) injects the
secrets into the shell environment when I cd
into the pulumi
directory.
Pulumi is a clear winner for me. Writing infrastructure code in a proper
language is liberating, and I appreciate how pulumi stack
neatly organizes my
infrastructure view.
Current stack is dev:
Managed by thinkpad
Last updated: 59 minutes ago (2025-06-13 07:42:44.338221653 +0200 CEST)
Pulumi version used: v3.175.0
Current stack resources (40):
TYPE NAME
pulumi:pulumi:Stack terraform-dev
├─ components:index:Cloudflare cloudflare
│ ├─ cloudflare:index/zone:Zone sarg.org.ru
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord MX 2
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord CNAME mail
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord MX 3
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord TXT dmarc
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord MX 1
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord TXT spf
│ │ ├─ cloudflare:index/dnsRecord:DnsRecord MX 0
│ │ └─ cloudflare:index/dnsRecord:DnsRecord TXT dkim
│ ├─ cloudflare:index/workersScript:WorkersScript blog
│ ├─ cloudflare:index/workersScript:WorkersScript tgbot
│ ├─ cloudflare:index/workersCustomDomain:WorkersCustomDomain blog
│ └─ cloudflare:index/workersCustomDomain:WorkersCustomDomain tgbot
├─ components:index:Google google
│ ├─ gcp:organizations/project:Project default
│ ├─ gcp:projects/service:Service services-apikeys.googleapis.com
│ ├─ gcp:projects/service:Service services-serviceusage.googleapis.com
│ ├─ gcp:projects/service:Service services-generativelanguage.googleapis.com
│ ├─ gcp:projects/service:Service services-cloudresourcemanager.googleapis.com
│ └─ gcp:projects/apiKey:ApiKey gptel
├─ components:index:Selectel selectel
│ ├─ openstack:networking/network:Network private
│ ├─ openstack:compute/keypair:Keypair ssh_key
│ ├─ openstack:networking/subnet:Subnet private
│ ├─ openstack:networking/router:Router router
│ ├─ openstack:networking/port:Port vpn
│ └─ openstack:networking/routerInterface:RouterInterface interface
├─ components:index:Hetzner hetzner
│ ├─ hcloud:index/sshKey:SshKey ssh_key
│ └─ hcloud:index/primaryIp:PrimaryIp vpn_ipv6
├─ pulumi:providers:telegram telegram
├─ components:index:Telegram telegram
│ ├─ telegram:index/botCommands:BotCommands diogenisbot
│ └─ telegram:index/botWebhook:BotWebhook diogenisbot
├─ pulumi:providers:openstack default_5_1_0
├─ pulumi:providers:hcloud default_1_23_0
├─ pulumi:providers:cloudflare default_6_2_1
└─ pulumi:providers:gcp default_8_33_0
Current stack outputs (0):
No output values currently in this stack
Use `pulumi stack select` to change stack; `pulumi stack ls` lists known ones