Pulumi vs Terraform: honest retrospective after a full migration
This post is part of my series on migrating my Homelab from Terraform to Pulumi. In this article, I will sum up the experience, highlight the highs and lows of Pulumi, and share a few things I wish I had known beforehand.
Other parts in this series:
- Why I am migrating my Homelab IaC from Terraform to Pulumi
- Migrating OVH DNS records from Terraform to Pulumi
- Migrating Proxmox LXC containers from Terraform to Pulumi
- How to manage Pulumi Secrets with 1Password
- Rookie mistakes I made with Pulumi dependency tracking
- Pulumi vs Terraform: honest retrospective after a full migration (this one)
Migration duration
This migration took me months to complete. I did not spend all my spare time on it, but it still dragged on because it was tedious and boring, and at some points even small progress required a significant investment of time.
Migrating network share configurations and putting together the foundations for the migration was probably the most painful part.
Preserving existing resources required careful manual imports and sometimes risky manual edits to the Pulumi state. It was not a clean import: LXC containers needed small tweaks and refreshes to be properly imported rather than recreated from scratch.
Once the foundations were in place, the rest moved fairly quickly, though not without its own quirks. The first 20% of the migration consumed roughly 80% of the total time. The remaining 80% took another 80%.
Missing foundations
Pulumi falls short when it comes to file and template management. There is no built-in way to create files from templates or to load templates and substitute variables the way Terraform does. I ended up writing a custom template system based on Handlebars so I could keep templates in isolated files with proper syntax highlighting and linter support.
Some Pulumi primitives also lack basic functionality.
RemoteFile requires you to manually create parent directories
if they are missing, and it does not support setting file permissions on the
remote. Both limitations are understandable given how differently operating
systems handle these things, but they are still annoying. I ended up writing
custom classes that wrap RemoteFile just to automate all of
this.
RemoteCommand only accepts in-memory strings, not script files.
So if you want to run a longer script, you either load it into memory and
pass it directly, or you copy the script to the remote host, set its
permissions, execute it with RemoteCommand, and then clean up
afterwards. Annoying. I wrote a custom class to handle that workflow too.
By the end I had a set of custom classes for the most common operations:
creating remote files from Handlebars templates, deploying configuration
files to paths that mirror the local assets folder structure,
and creating executable files from either templates or static sources.
Everything became much simpler, faster, and more pleasant to work with once
these abstractions were in place.
But I’m overall happy
Despite all the annoyances and custom code I had to write, I still prefer this over Terraform. Being able to import shared constants or write unit tests is great.
I wrote my stack in TypeScript, which means I can also statically derive information that would simply be impossible in Terraform (like the IP addresses of each container or the domain names linked to them) even from files far from where those values are defined, just by leveraging TypeScript generics and compiler inference.
Being able to structure files in whatever folder layout I prefer is also really nice, and my service definitions became much cleaner and easier to read thanks to splitting the code in a more composable way than Terraform ever allowed.
Now that I have invested in these foundations, I will keep using Pulumi and build more things on top of it. That said, had I known the time and effort it would take to reach this point, I would have delayed the migration.
There are real benefits to Pulumi over Terraform, but at the time I simply wanted to add another LXC container to my homelab without repeating too much boilerplate and prop drilling. All this effort was not worth it just for those small improvements.
It is worth it, though, for the broader improvements to the entire homelab stack: the better development experience that comes from type-checking and inference, the powerful code reuse, the simpler modules and utilities.
I also used this migration as an excuse to fix things that had been wrong in the original server configuration: setting up SMB and CIFS properly for network shares, unifying all ZSH configuration for Debian, migrating from Oh My ZSH to Oh My Posh, and even automating the installation of services that were previously set up by hand.
So, if you are starting from scratch, absolutely give Pulumi a try. If you have an existing stack and are considering a migration, double whatever time estimate you have, and if it still makes sense after that, go for it.
No replies on “Pulumi vs Terraform: honest retrospective after a full migration”