· 9 min read
Notes On Learning OpenTofu
Having worked in the middle of nowhere, I was mostly dealing with on-prem hardware and subsequently I used Ansible often for configuration management. The times I did work with the cloud I generally would deploy something to a single VPS instance add on a database instance and then set up DNS and backups. This type of infrastructure I would set up once and modify rarely and then I would automate the application deployments to the VPS via Ansible. So, at the time I did not see the need to invest in learning Terraform or similar tools. But, that has changed.
Now, I am working towards fully automating the deployment of my personal projects which means I want to fully declare my infrastructure in code.
Terraform vs OpenTofu
I remember hearing of the OpenTofu fork on a Fireship video.
So now looking into it myself 18 months on I can find OpenTofu is surviving and is building new features, slowly distancing itself from Terraform. Although in practice I find them still nearly interchangeable.
While trying to see how the online community felt about OpenTofu since its fork I found a lot of initial “Why should I care about OpenTofu” posts. However, it was clear any recent mentions of OpenTofu online were a lot more positive and supportive of new feature releases. This was enough for me that I would begin using OpenTofu.
My previous track record of researching options and adopting a particular technology has worked well in the past (Vue/FastAPI/Astro) so I wasn’t so worried by this decision on admittedly loose criteria. It helps that if I am wrong later I should be able to transfer my knowledge to Terraform with minimal headache.
Pulumi???
I did look at other options Pulumi stood out, but I gather my simple use cases do not require its features. I may like to look at it in the future but for now OpenTofu is sufficient.
Key Resources
I found the video “Complete Terraform Course” by DevOps Director on YouTube with accompanying Github repo to follow along a perfect starter. It had just the right learning curve to ease into working with Terraform — or OpenTofu!
However, it is a bit opinionated and took a straight path from single file configuration to a configuration with modules and multiple environments. When I was working on my own configuration I felt I was missing context on how much abstraction I needed and when would I need a certain level of abstraction and that’s where I found “Evolving Your Infrastructure with Terraform” from HasiCorp.
The talk demystified the progression from a single file to a progressively more modular configuration. It went further in complexity than I needed which was great to see and let me settle on my own level of abstraction I needed for my configuration.
Design
As a reminder my goal is to automate the complete provisioning and deployment of my personal projects. I can fit my projects on a single VPS so I will need to design around that constraint. Following what I’ve learned I believe a Terramod setup would be sufficient for me; however, I decided to settle on a Terraservices pattern.
The Terraservices pattern allows me to write each project’s required infrastructure as an independent unit so later I can easily add and remove projects from the shared infrastructure. I also learned with this pattern I could write each project’s IaC as separate git repos, but as an individual owning all my own projects that’s unnecessary complexity.
For my environments I opted to forego using workspaces because, coming from a Python background, I prefer explicit over implicit. But more seriously, I just don’t like environments being hidden behind the tool and I don’t mind writing a bit extra while I’m learning to avoid over-engineering at this stage. This tracks well with my preference for WET code too.
Therefore, I’ll model both Ansible and OpenTofu with the similar structures in regards to environments
and roles
or modules
respectively.
OpenTofu
Following my Terraservices pattern I’ll define services in each environment. One global
service for provisioning the shared VPS and then additional services for each project.
The Global Service
The global service does the most and at the moment is tightly coupled with its Ansible counterpart. When a VPS is provisioned I then use local-exec
to configure the VPS with a user and include my SSH key. Then I follow up with the global service’s Ansible playbook which hardens the server. Lastly, I provision a Linode firewall for the VPS.
Above you can see the ugly and brittle ../../../../ansible
paths which really need to be decoupled. But, for now it is nice have a VPS ready to SSH into from a single tofu apply
command. Later I will consider decoupling Ansible.
The Project Services
The project services are simple. Each of my projects are fairly self-contained in their Docker compose files so I am mostly provisioning DNS records in these services.
Each begins by grabbing the outputs from the global
service then calling their respective project’s module. In this project I only need to set the DNS records to point to the global
service’s VPS.
NOTE: I’m using a different provider for DNS instead of the Linode domain manager.
Ansible
After the OpenTofu services are provisioned I then move to my ansible
directory to configure and deploy each project. It’s immediately obvious there is a lot more going on here.
Each playbook will configure or deploy a particular project. Given each project is sharing the VPS and multiple projects need Docker and Nginx those projects will have similar playbooks which call the common
, docker
and nginx
roles. However, when it comes to deploying each project, then the playbooks diverge and will use their unique project templates
and such.
The Project Playbooks
Just like the OpenTofu services before, I make use of roles
to create generic deployment playbooks for a project. Then call each playbook with the appropriate environment to deploy it for the VPS as indended.
Environment specific variables are stored in the ansible/environments/${environment_name}/vars
directory.
This project is deployed via the following command which loads the appropriate environment.
And if you missed it earlier I generated the Ansible inventory files in the global
OpenTofu service.
Moving Forward
I do not like the coupling in the global
service between OpenTofu and Ansible. I would consider using a script or Make
file at the root of the codebase to run OpenTofu followed by Ansible. It would be nice to have the same single command to provision and deploy a single project.
All in all, the learning experience has been good and I will be continuing to migrate my individual projects’ deployment scripts into this project structure.