<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: todoroff</title>
    <description>The latest articles on Forem by todoroff (@todoroff).</description>
    <link>https://forem.com/todoroff</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3617257%2F993b6574-6c4e-43e5-ba61-cd7ddfa29f00.png</url>
      <title>Forem: todoroff</title>
      <link>https://forem.com/todoroff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/todoroff"/>
    <language>en</language>
    <item>
      <title>Terraform for Local VMs: A Modern Alternative to Vagrant</title>
      <dc:creator>todoroff</dc:creator>
      <pubDate>Wed, 07 Jan 2026 10:45:03 +0000</pubDate>
      <link>https://forem.com/todoroff/terraform-for-local-vms-a-modern-alternative-to-vagrant-3j23</link>
      <guid>https://forem.com/todoroff/terraform-for-local-vms-a-modern-alternative-to-vagrant-3j23</guid>
      <description>&lt;p&gt;After 8 years of using Vagrant, I finally made the switch. Not because Vagrant stopped working - it still does what it's always done. But because I found something that fits better into my modern infrastructure-as-code workflow.&lt;/p&gt;

&lt;p&gt;Here's the story of how I replaced Vagrant with &lt;strong&gt;Multipass + Terraform&lt;/strong&gt; and never looked back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Vagrant is Showing Its Age
&lt;/h2&gt;

&lt;p&gt;Don't get me wrong - Vagrant revolutionized local development. The idea of "infrastructure as code" for your laptop was groundbreaking in 2010.&lt;/p&gt;

&lt;p&gt;But in 2026, I kept hitting the same pain points:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Provider lock-in&lt;/strong&gt;&lt;br&gt;
Tied to VirtualBox (slow) or VMware (expensive). Hyper-V support was always "experimental."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Heavy box downloads&lt;/strong&gt;&lt;br&gt;
Every &lt;code&gt;vagrant up&lt;/code&gt; on a new project meant downloading multi-GB box files.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤔 Different workflow&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;Vagrantfile&lt;/code&gt; syntax is Ruby DSL. My production infrastructure is Terraform. Why maintain two mental models?&lt;/p&gt;

&lt;p&gt;Sound familiar?&lt;/p&gt;
&lt;h2&gt;
  
  
  The Discovery: Multipass + Terraform
&lt;/h2&gt;

&lt;p&gt;Then I discovered &lt;a href="https://multipass.run/" rel="noopener noreferrer"&gt;Canonical Multipass&lt;/a&gt; - a lightweight VM manager from Canonical.&lt;/p&gt;

&lt;p&gt;Combined with a Terraform provider, I could:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Use the same IaC approach locally and in production&lt;/li&gt;
&lt;li&gt;✅ Native cloud-init support (just like AWS/GCP/Azure)&lt;/li&gt;
&lt;li&gt;✅ Simpler setup - no VirtualBox Guest Additions hassles&lt;/li&gt;
&lt;li&gt;✅ One workflow to rule them all&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me show you how to make the switch.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 1: Understanding the Difference
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Vagrant's Approach
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Vagrantfile&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu/jammy64"&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.56.10"&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2048"&lt;/span&gt;
    &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cpus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
    apt-get update
    apt-get install -y nginx
&lt;/span&gt;&lt;span class="no"&gt;  SHELL&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Ruby DSL&lt;/li&gt;
&lt;li&gt;Provider-specific configurations&lt;/li&gt;
&lt;li&gt;Shell provisioners (or Ansible/Chef/Puppet)&lt;/li&gt;
&lt;li&gt;Box-based images&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Multipass + Terraform Approach
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;multipass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"todoroff/multipass"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-server"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;

  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    #cloud-config
    packages:
      - nginx
&lt;/span&gt;&lt;span class="no"&gt;  EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Standard HCL (same as production Terraform)&lt;/li&gt;
&lt;li&gt;Cloud-init provisioning (same as cloud VMs)&lt;/li&gt;
&lt;li&gt;Image aliases (no giant downloads)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Same result, different philosophy.&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 2: Setting Up Multipass + Terraform
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Step 1: Install Multipass
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;multipass

&lt;span class="c"&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;multipass

&lt;span class="c"&gt;# Windows&lt;/span&gt;
&lt;span class="c"&gt;# Download from https://multipass.run/install&lt;/span&gt;
winget &lt;span class="nb"&gt;install &lt;/span&gt;Canonical.Multipass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Verify it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass version
&lt;span class="c"&gt;# multipass   1.14.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Create Your First Terraform Config
&lt;/h3&gt;

&lt;p&gt;Create a new directory and &lt;code&gt;main.tf&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;multipass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"todoroff/multipass"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.5"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"multipass"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Optional: explicit path to multipass binary&lt;/span&gt;
  &lt;span class="c1"&gt;# multipass_path = "/usr/local/bin/multipass"&lt;/span&gt;

  &lt;span class="c1"&gt;# Optional: timeout for commands (default 120s)&lt;/span&gt;
  &lt;span class="nx"&gt;command_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-dev-vm"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"4G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;  &lt;span class="c1"&gt;# Ubuntu 22.04 LTS&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ip_address"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Your VM is running. ⚡&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check it&lt;/span&gt;
multipass list
&lt;span class="c"&gt;# Name       State    IPv4            Image&lt;/span&gt;
&lt;span class="c"&gt;# my-dev-vm  Running  192.168.64.5    Ubuntu 22.04 LTS&lt;/span&gt;

&lt;span class="c"&gt;# SSH into it&lt;/span&gt;
multipass shell my-dev-vm
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 3: Migrating Your Vagrantfile
&lt;/h2&gt;

&lt;p&gt;Let's convert a real-world Vagrant setup to Multipass + Terraform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before: Vagrant Multi-Machine Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Vagrantfile&lt;/span&gt;
&lt;span class="no"&gt;Vagrant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu/jammy64"&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db-server"&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.56.10"&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2048"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
      apt-get update
      apt-get install -y postgresql postgresql-contrib
      sudo -u postgres createuser --superuser vagrant
      sudo -u postgres createdb myapp
&lt;/span&gt;&lt;span class="no"&gt;    SHELL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-server"&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;network&lt;/span&gt; &lt;span class="s2"&gt;"private_network"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;ip: &lt;/span&gt;&lt;span class="s2"&gt;"192.168.56.11"&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"virtualbox"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="n"&gt;vb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1024"&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;provision&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;inline: &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;SHELL&lt;/span&gt;&lt;span class="sh"&gt;
      apt-get update
      apt-get install -y nginx
      echo "upstream api { server 192.168.56.10:5432; }" &amp;gt; /etc/nginx/conf.d/upstream.conf
&lt;/span&gt;&lt;span class="no"&gt;    SHELL&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  After: Multipass + Terraform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;multipass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"todoroff/multipass"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 1.5"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"multipass"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# Database Server&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"db"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db-server"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;

  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    #cloud-config
    package_update: true
    packages:
      - postgresql
      - postgresql-contrib
    runcmd:
      - sudo -u postgres createuser --superuser ubuntu
      - sudo -u postgres createdb myapp
&lt;/span&gt;&lt;span class="no"&gt;  EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Web Server (depends on DB for its IP)&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"web"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-server"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;

  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/web-init.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;db_host&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Outputs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"db_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"web_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# web-init.yaml&lt;/span&gt;
&lt;span class="c1"&gt;#cloud-config&lt;/span&gt;
&lt;span class="na"&gt;package_update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;write_files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/conf.d/upstream.conf&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;upstream api {&lt;/span&gt;
        &lt;span class="s"&gt;server ${db_host}:5432;&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
&lt;span class="na"&gt;runcmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;systemctl restart nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Part 4: Advanced Features You'll Love
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Snapshots (Save Your Progress!)
&lt;/h3&gt;

&lt;p&gt;One killer feature the Vagrant workflow always lacked: &lt;strong&gt;native snapshots&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"test_env"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test-env"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;
  &lt;span class="c1"&gt;# ... config&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Take a snapshot before risky operations&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_snapshot"&lt;/span&gt; &lt;span class="s2"&gt;"before_upgrade"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test_env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pre-upgrade-backup"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run your tests&lt;/li&gt;
&lt;li&gt;Break things&lt;/li&gt;
&lt;li&gt;Restore to snapshot&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All managed through Terraform state.&lt;/p&gt;

&lt;h3&gt;
  
  
  File Transfers (No More Provisioners!)
&lt;/h3&gt;

&lt;p&gt;Remember the pain of synced folders and file provisioners?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Upload config files&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_file_upload"&lt;/span&gt; &lt;span class="s2"&gt;"app_config"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/configs/app.yaml"&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/etc/myapp/config.yaml"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Download logs for analysis&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_file_download"&lt;/span&gt; &lt;span class="s2"&gt;"app_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/var/log/myapp/app.log"&lt;/span&gt;
  &lt;span class="nx"&gt;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/logs/app.log"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;config.vm.synced_folder&lt;/code&gt; headaches&lt;/li&gt;
&lt;li&gt;NFS permission issues&lt;/li&gt;
&lt;li&gt;VirtualBox Guest Additions breaking&lt;/li&gt;
&lt;li&gt;rsync timing problems&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Aliases (Quick Access)
&lt;/h3&gt;

&lt;p&gt;Create host-level shortcuts to jump into your VMs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_alias"&lt;/span&gt; &lt;span class="s2"&gt;"db_shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"db-psql"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"sudo -u postgres psql myapp"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_alias"&lt;/span&gt; &lt;span class="s2"&gt;"web_logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"web-logs"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tail -f /var/log/nginx/access.log"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now from your host terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;db-psql      &lt;span class="c"&gt;# Instantly opens psql on db server&lt;/span&gt;
web-logs     &lt;span class="c"&gt;# Tails nginx logs&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No more &lt;code&gt;vagrant ssh web -c "..."&lt;/code&gt; gymnastics.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local Kubernetes Clusters
&lt;/h3&gt;

&lt;p&gt;Want to spin up a full multi-node K3s cluster for testing? I wrote a complete step-by-step tutorial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📚 &lt;a href="https://dev.to/todoroff/build-a-local-kubernetes-cluster-in-minutes-with-terraform-and-multipass-14e4"&gt;Build a Local Kubernetes Cluster in Minutes with Terraform and Multipass&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The tutorial walks through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;1 master + 2 worker nodes&lt;/li&gt;
&lt;li&gt;Automatic cluster joining via cloud-init&lt;/li&gt;
&lt;li&gt;kubectl access from your host machine&lt;/li&gt;
&lt;li&gt;Complete working code you can deploy today&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Common Use Cases
&lt;/h3&gt;

&lt;p&gt;This stack is also perfect for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🔧 Infrastructure Testing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test Ansible playbooks across multiple nodes&lt;/li&gt;
&lt;li&gt;Validate HAProxy or Nginx load balancer configs&lt;/li&gt;
&lt;li&gt;Simulate distributed systems locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;🎓 Learning Labs&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Practice Terraform without cloud costs&lt;/li&gt;
&lt;li&gt;Learn Linux system administration&lt;/li&gt;
&lt;li&gt;Experiment with databases and replication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;💻 Development Environments&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reproducible team dev environments&lt;/li&gt;
&lt;li&gt;Test upgrades in isolation&lt;/li&gt;
&lt;li&gt;Quick throwaway VMs for experimentation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Part 6: Migration Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Vagrant&lt;/th&gt;
&lt;th&gt;Multipass + Terraform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vagrant up&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform apply&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vagrant destroy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;terraform destroy&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vagrant ssh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;multipass shell &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vagrant halt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(use &lt;code&gt;multipass stop &amp;lt;name&amp;gt;&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vagrant reload&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;terraform apply&lt;/code&gt; (with changes)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.vm.box&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;image = "jammy"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vb.memory&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;memory = "2G"&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vb.cpus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cpus = 2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.vm.provision "shell"&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cloud_init = &amp;lt;&amp;lt;-EOT ... EOT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.vm.synced_folder&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;multipass_file_upload&lt;/code&gt; resource&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;config.vm.network&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;networks { }&lt;/code&gt; block&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Part 7: When to Stick with Vagrant
&lt;/h2&gt;

&lt;p&gt;To be fair, Vagrant still has its place:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Use Vagrant if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need non-Ubuntu operating systems (Windows, CentOS, etc.)&lt;/li&gt;
&lt;li&gt;You're locked into VMware or Hyper-V for specific reasons&lt;/li&gt;
&lt;li&gt;Your team has heavy investment in existing Vagrantfiles&lt;/li&gt;
&lt;li&gt;You need VirtualBox-specific features&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Use Multipass + Terraform if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're working primarily with Ubuntu/Linux&lt;/li&gt;
&lt;li&gt;You want a simpler, lighter setup&lt;/li&gt;
&lt;li&gt;You want one workflow for local and cloud&lt;/li&gt;
&lt;li&gt;You're already using Terraform&lt;/li&gt;
&lt;li&gt;You want modern cloud-init provisioning&lt;/li&gt;
&lt;li&gt;Resource efficiency is important&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting Started Today
&lt;/h2&gt;

&lt;p&gt;Ready to make the switch? Here's your 5-minute quickstart:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Install Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install Multipass&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;multipass  &lt;span class="c"&gt;# or snap install multipass&lt;/span&gt;

&lt;span class="c"&gt;# Verify&lt;/span&gt;
multipass version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Create Your First Config
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;my-dev-env &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;my-dev-env

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; main.tf &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
terraform {
  required_providers {
    multipass = {
      source = "todoroff/multipass"
    }
  }
}

resource "multipass_instance" "dev" {
  name   = "dev-box"
  cpus   = 2
  memory = "4G"
  disk   = "20G"
  image  = "jammy"

  cloud_init = &amp;lt;&amp;lt;-EOT
    #cloud-config
    packages:
      - git
      - curl
      - build-essential
    runcmd:
      - echo "Dev environment ready!" &amp;gt; /home/ubuntu/welcome.txt
  EOT
}

output "ip" {
  value = multipass_instance.dev.ipv4[0]
}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Deploy
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
terraform apply &lt;span class="nt"&gt;-auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4. Access Your VM
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass shell dev-box
&lt;span class="nb"&gt;cat&lt;/span&gt; ~/welcome.txt
&lt;span class="c"&gt;# Dev environment ready!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;That's it.&lt;/strong&gt; You're running a modern, fast, Terraform-managed local development environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform Provider:&lt;/strong&gt; &lt;a href="https://registry.terraform.io/providers/todoroff/multipass" rel="noopener noreferrer"&gt;registry.terraform.io/providers/todoroff/multipass&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub Repository:&lt;/strong&gt; &lt;a href="https://github.com/todoroff/terraform-provider-multipass" rel="noopener noreferrer"&gt;github.com/todoroff/terraform-provider-multipass&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multipass Official:&lt;/strong&gt; &lt;a href="https://multipass.run" rel="noopener noreferrer"&gt;multipass.run&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example Configurations:&lt;/strong&gt; &lt;a href="https://github.com/todoroff/terraform-provider-multipass/tree/master/examples" rel="noopener noreferrer"&gt;GitHub Examples&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Switching from Vagrant to Multipass + Terraform was one of the best decisions I made for my local development workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I gained:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔧 Simpler setup (no Guest Additions, NFS issues, etc.)&lt;/li&gt;
&lt;li&gt;🔄 One IaC workflow for everything&lt;/li&gt;
&lt;li&gt;📸 Native snapshot support&lt;/li&gt;
&lt;li&gt;🎯 Cloud-native provisioning with cloud-init&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I lost:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-OS support (Ubuntu-only)&lt;/li&gt;
&lt;li&gt;...that's about it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want a modern, Terraform-native approach to local VMs, give this a try.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Have questions about migrating your Vagrant setup?&lt;/strong&gt; Drop them in the comments! I'm happy to help with specific migration scenarios.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Found this useful?&lt;/strong&gt; Consider ⭐ starring the &lt;a href="https://github.com/todoroff/terraform-provider-multipass" rel="noopener noreferrer"&gt;provider on GitHub&lt;/a&gt; - it helps others discover it!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;What's your local development setup? Still on Vagrant, Docker Compose, or something else entirely? Let me know in the comments! 👇&lt;/em&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>devops</category>
      <category>vagrant</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a Local Kubernetes Cluster in Minutes with Terraform and Multipass</title>
      <dc:creator>todoroff</dc:creator>
      <pubDate>Sun, 23 Nov 2025 00:05:51 +0000</pubDate>
      <link>https://forem.com/todoroff/build-a-local-kubernetes-cluster-in-minutes-with-terraform-and-multipass-14e4</link>
      <guid>https://forem.com/todoroff/build-a-local-kubernetes-cluster-in-minutes-with-terraform-and-multipass-14e4</guid>
      <description>&lt;p&gt;Are you looking for a way to spin up a lightweight, throwaway Kubernetes cluster on your local machine without the overhead of Docker Desktop or Minikube? Or maybe you want to simulate a multi-node environment to test node affinity and failover?&lt;/p&gt;

&lt;p&gt;In this article, we'll show you how to build a &lt;strong&gt;multi-node K3s cluster&lt;/strong&gt; completely from code using the &lt;strong&gt;Multipass Terraform Provider&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Stack?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Multipass&lt;/strong&gt;: Canonical's lightweight VM manager for Linux, Windows, and macOS. It spins up Ubuntu instances in seconds.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Terraform&lt;/strong&gt;: The industry standard for Infrastructure as Code (IaC). It manages the lifecycle, dependencies, and configuration of your VMs.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;K3s&lt;/strong&gt;: A highly available, certified Kubernetes distribution designed for production workloads in unattended, resource-constrained, remote locations or inside IoT appliances.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining these, you get a reproducible, codified local lab environment that you can spin up and tear down with a single command.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Project: A 3-Node K3s Cluster
&lt;/h2&gt;

&lt;p&gt;We are going to build:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;1 Master Node&lt;/strong&gt;: Runs the K3s control plane.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;2 Worker Nodes&lt;/strong&gt;: Join the cluster automatically.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Automatic Wiring&lt;/strong&gt;: Terraform will handle passing the master's IP and a shared secret to the workers.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;a href="https://multipass.run/install" rel="noopener noreferrer"&gt;Install Multipass&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt; &lt;a href="https://developer.hashicorp.com/terraform/install" rel="noopener noreferrer"&gt;Install Terraform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initialize a new directory for your project:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;k3s-lab &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;k3s-lab
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Terraform Configuration
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;main.tf&lt;/code&gt; and paste the following code. We're using the &lt;code&gt;todoroff/multipass&lt;/code&gt; provider to manage our VMs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;multipass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"todoroff/multipass"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"multipass"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Increase timeout for image downloads and initialization&lt;/span&gt;
  &lt;span class="nx"&gt;command_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt; 
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Define a shared secret for the cluster&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;k3s_token&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-super-secret-shared-token-12345"&lt;/span&gt;

  &lt;span class="c1"&gt;# Cloud-init script for the master node&lt;/span&gt;
  &lt;span class="c1"&gt;# Installs K3s server, creates a wrapper script for kubectl, and sets the token&lt;/span&gt;
  &lt;span class="nx"&gt;master_cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    #cloud-config
    package_update: true
    package_upgrade: true
    write_files:
      - path: /usr/local/bin/k3s-kubectl-wrapper
        permissions: '0755'
        content: |
          #!/bin/sh
          sudo k3s kubectl "$@"
    runcmd:
      - curl -sfL https://get.k3s.io | K3S_TOKEN=${local.k3s_token} sh -s - server --cluster-init
&lt;/span&gt;&lt;span class="no"&gt;  EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Create the Master Node&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"k3s_master"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"k3s-master"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt; &lt;span class="c1"&gt;# Ubuntu 22.04 LTS&lt;/span&gt;

  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;master_cloud_init&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 3. Create Worker Nodes&lt;/span&gt;
&lt;span class="c1"&gt;# These depend on the master because they need its IP address&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"k3s_worker"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;count&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"k3s-worker-${count.index + 1}"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5G"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"jammy"&lt;/span&gt;

  &lt;span class="c1"&gt;# Cloud-init script for workers&lt;/span&gt;
  &lt;span class="c1"&gt;# Uses the master's IP (from Terraform state) to join the cluster&lt;/span&gt;
  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    #cloud-config
    package_update: true
    runcmd:
      - curl -sfL https://get.k3s.io | K3S_URL=https://${multipass_instance.k3s_master.ipv4[0]}:6443 K3S_TOKEN=${local.k3s_token} sh -
&lt;/span&gt;&lt;span class="no"&gt;  EOT

&lt;/span&gt;  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;k3s_master&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 4. Create a convenient alias to run kubectl from your host&lt;/span&gt;
&lt;span class="c1"&gt;# We use a wrapper script to handle argument passing correctly via alias&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_alias"&lt;/span&gt; &lt;span class="s2"&gt;"kubectl"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"k3s-kubectl"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;k3s_master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/local/bin/k3s-kubectl-wrapper"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# 5. Output the node IPs&lt;/span&gt;
&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_nodes"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;master&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;k3s_master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;
    &lt;span class="nx"&gt;workers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;k3s_worker&lt;/span&gt;&lt;span class="p"&gt;[*].&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How It Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;&lt;code&gt;cloud_init&lt;/code&gt; Magic&lt;/strong&gt;: We use Terraform's &lt;code&gt;local&lt;/code&gt; variables to construct a cloud-init script. This script runs on first boot.

&lt;ul&gt;
&lt;li&gt;  On the &lt;strong&gt;master&lt;/strong&gt;, it downloads and installs K3s in server mode. It also creates a helper script &lt;code&gt;/usr/local/bin/k3s-kubectl-wrapper&lt;/code&gt; to make running kubectl commands easier via alias.&lt;/li&gt;
&lt;li&gt;  On the &lt;strong&gt;workers&lt;/strong&gt;, it installs K3s and immediately joins the cluster using the &lt;code&gt;K3S_URL&lt;/code&gt; environment variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Dynamic Configuration&lt;/strong&gt;: Notice &lt;code&gt;${multipass_instance.k3s_master.ipv4[0]}&lt;/code&gt; in the worker's cloud-init. Terraform waits for the master to be created, grabs its first IPv4 address, injects it into the worker's configuration, and &lt;em&gt;then&lt;/em&gt; creates the workers. No manual copy-pasting required!&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Aliases&lt;/strong&gt;: The &lt;code&gt;multipass_alias&lt;/code&gt; resource creates a &lt;code&gt;k3s-kubectl&lt;/code&gt; command on your host machine that invokes the wrapper script on the master node.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Deploying the Lab
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Initialize Terraform&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Apply the Configuration&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; when prompted.&lt;/p&gt;

&lt;p&gt;Terraform will spin up the VMs. It might take a minute or two for the VMs to boot and for K3s to install.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Using Your Cluster
&lt;/h2&gt;

&lt;p&gt;Once Terraform finishes, wait about 60 seconds for the cloud-init scripts to complete installation inside the VMs.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Check the Nodes&lt;/strong&gt;:&lt;br&gt;
You can run &lt;code&gt;kubectl&lt;/code&gt; commands directly from your terminal using the alias we created:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass k3s-kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;You should see something like:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME           STATUS   ROLES                       AGE   VERSION
k3s-master     Ready    control-plane,etcd,master   2m    v1.28.2+k3s1
k3s-worker-1   Ready    &amp;lt;none&amp;gt;                      1m    v1.28.2+k3s1
k3s-worker-2   Ready    &amp;lt;none&amp;gt;                      1m    v1.28.2+k3s1
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deploy a Test Workload&lt;/strong&gt;:&lt;br&gt;
Deploy a simple Nginx server to verify everything is working:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass k3s-kubectl create deployment nginx &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx
multipass k3s-kubectl expose deployment nginx &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80 &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NodePort
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;When you're done with your lab, you don't need to manually delete VMs or clean up files. Just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will stop and delete all the Multipass instances and remove the aliases, leaving your machine clean.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Multipass Terraform Provider&lt;/strong&gt; turns your local machine into a flexible cloud environment. Whether you're testing Kubernetes, setting up a complex microservices mesh, or just need a clean Linux sandbox, you can define it all in code.&lt;/p&gt;

&lt;p&gt;Check out the &lt;a href="https://registry.terraform.io/providers/todoroff/multipass/latest/docs" rel="noopener noreferrer"&gt;provider documentation&lt;/a&gt; for more resources like &lt;code&gt;multipass_mount&lt;/code&gt; and &lt;code&gt;multipass_snapshot&lt;/code&gt; to take your local labs to the next level!&lt;/p&gt;

&lt;p&gt;Or some of the examples in the provider repository: &lt;a href="https://github.com/todoroff/terraform-provider-multipass" rel="noopener noreferrer"&gt;https://github.com/todoroff/terraform-provider-multipass&lt;/a&gt;&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>kubernetes</category>
      <category>tutorial</category>
      <category>devops</category>
    </item>
    <item>
      <title>Multipass + Terraform: Modern VM Automation Guide</title>
      <dc:creator>todoroff</dc:creator>
      <pubDate>Tue, 18 Nov 2025 10:09:46 +0000</pubDate>
      <link>https://forem.com/todoroff/multipass-terraform-modern-vm-automation-guide-129l</link>
      <guid>https://forem.com/todoroff/multipass-terraform-modern-vm-automation-guide-129l</guid>
      <description>&lt;p&gt;This article introduces the &lt;a href="https://github.com/todoroff/terraform-provider-multipass" rel="noopener noreferrer"&gt;&lt;code&gt;todoroff/multipass&lt;/code&gt;&lt;/a&gt; Terraform provider: a modern, plugin-framework-based provider that exposes Multipass features as declarative infrastructure. We’ll walk through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Basic instance management&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple NICs and static IPs&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inline and templated cloud-init&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;File upload/download without provisioners&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patterns for Ansible and dev/CI workflows&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The examples target Terraform ≥ 1.6 and Multipass ≥ 1.13.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why another Multipass provider?
&lt;/h2&gt;

&lt;p&gt;There is an existing Multipass provider out in the wild (&lt;a href="https://github.com/larstobi/terraform-provider-multipass/issues?q=is%3Aissue%20feature" rel="noopener noreferrer"&gt;&lt;code&gt;larstobi/terraform-provider-multipass&lt;/code&gt;&lt;/a&gt;), but users have requested features like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Network interfaces &amp;amp; predictable IP handling&lt;/li&gt;
&lt;li&gt;Better cloud-init ergonomics&lt;/li&gt;
&lt;li&gt;More expressive networking and file transfer support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This provider focuses on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rich instance schema&lt;/strong&gt;: CPU, memory, disk, &lt;strong&gt;multiple networks&lt;/strong&gt;, mounts, cloud-init (file or inline), auto-recovery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking primitives&lt;/strong&gt;: &lt;code&gt;networks&lt;/code&gt; block that maps directly to &lt;code&gt;multipass networks&lt;/code&gt; and supports &lt;strong&gt;multiple NICs&lt;/strong&gt; with explicit MACs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-class cloud-init&lt;/strong&gt;: &lt;code&gt;cloud_init_file&lt;/code&gt; or &lt;code&gt;cloud_init&lt;/code&gt; inline, with clear mutual exclusion and templating support.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File workflows&lt;/strong&gt;: &lt;code&gt;multipass_file_upload&lt;/code&gt; and &lt;code&gt;multipass_file_download&lt;/code&gt; as alternatives to brittle provisioners.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean diagnostics&lt;/strong&gt;: backed by a dedicated CLI parsing layer for better error messages.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Add the provider to your Terraform configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;multipass&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"todoroff/multipass"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.4.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"multipass"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;# Optional overrides&lt;/span&gt;
  &lt;span class="nx"&gt;multipass_path&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/usr/bin/multipass"&lt;/span&gt;
  &lt;span class="nx"&gt;command_timeout&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;180&lt;/span&gt;          &lt;span class="c1"&gt;# seconds&lt;/span&gt;
  &lt;span class="nx"&gt;default_image&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lts"&lt;/span&gt;        &lt;span class="c1"&gt;# fallback image alias&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  A basic VM with networking and an alias
&lt;/h2&gt;

&lt;p&gt;First, let’s create a simple dev box, attach it to a host NIC, and expose a convenient alias:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"dev"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev-box"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lts"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"4G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"15G"&lt;/span&gt;

  &lt;span class="c1"&gt;# Attach to a specific host NIC from `multipass networks`&lt;/span&gt;
  &lt;span class="nx"&gt;networks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en0"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Optional host mount&lt;/span&gt;
  &lt;span class="nx"&gt;mounts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;host_path&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/home/USERNAME/projects"&lt;/span&gt;
    &lt;span class="nx"&gt;instance_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/workspace"&lt;/span&gt;
    &lt;span class="nx"&gt;read_only&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;primary&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;auto_recover&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_alias"&lt;/span&gt; &lt;span class="s2"&gt;"shell"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev-shell"&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;command&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bash"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"dev_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;networks&lt;/code&gt; block&lt;/strong&gt;: Binds the VM to a host network (e.g. &lt;code&gt;en0&lt;/code&gt; on macOS) discovered via &lt;code&gt;multipass networks&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ipv4&lt;/code&gt; output&lt;/strong&gt;: Exposes the VM’s IP(s) for use in other Terraform resources or inventories.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Multiple NICs and deterministic MACs
&lt;/h2&gt;

&lt;p&gt;Multipass supports multiple NICs per instance; this provider lets you express that with repeated &lt;code&gt;networks&lt;/code&gt; blocks. Each entry becomes a &lt;code&gt;--network&lt;/code&gt; flag under the hood.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"router"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lab-router"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"22.04"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"8G"&lt;/span&gt;

  &lt;span class="c1"&gt;# NIC 1: Wi-Fi&lt;/span&gt;
  &lt;span class="nx"&gt;networks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en0"&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"manual"&lt;/span&gt;
    &lt;span class="nx"&gt;mac&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"52:54:00:4b:ab:bd"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# NIC 2: Wired&lt;/span&gt;
  &lt;span class="nx"&gt;networks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en5"&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"manual"&lt;/span&gt;
    &lt;span class="nx"&gt;mac&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"52:54:00:4b:ab:cd"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern is useful when you care about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stable MACs&lt;/strong&gt; for DHCP reservations.&lt;/li&gt;
&lt;li&gt;Traffic separation across multiple host interfaces.&lt;/li&gt;
&lt;li&gt;Lab setups that mimic routers or multi-homed hosts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Static IPs with cloud-init + Netplan
&lt;/h2&gt;

&lt;p&gt;If you want &lt;strong&gt;static IPs inside the guest&lt;/strong&gt;, the recommended approach is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use a &lt;strong&gt;fixed MAC&lt;/strong&gt; in the &lt;code&gt;networks&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;cloud-init&lt;/strong&gt; to write a Netplan config that assigns a static IP to that MAC.&lt;/li&gt;
&lt;li&gt;Apply Netplan on boot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here’s a complete example using inline cloud-init:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"static"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lab-node-1"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"22.04"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"2G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"5G"&lt;/span&gt;

  &lt;span class="c1"&gt;# Stable MAC on a specific host NIC&lt;/span&gt;
  &lt;span class="nx"&gt;networks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"en0"&lt;/span&gt;
    &lt;span class="nx"&gt;mode&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"manual"&lt;/span&gt;
    &lt;span class="nx"&gt;mac&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"52:54:00:4b:ab:bd"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Inline cloud-init configuring a static IPv4 via Netplan&lt;/span&gt;
  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;EOT&lt;/span&gt;&lt;span class="sh"&gt;
    #cloud-config
    write_files:
      - path: /etc/netplan/10-custom.yaml
        permissions: "0644"
        content: |
          network:
            version: 2
            ethernets:
              extra0:
                dhcp4: no
                match:
                  macaddress: "52:54:00:4b:ab:bd"
                addresses: ["192.168.64.97/24"]
    runcmd:
      - netplan apply
&lt;/span&gt;&lt;span class="no"&gt;  EOT
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"static_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cloud_init&lt;/code&gt;&lt;/strong&gt; is a normal Terraform string; you can also feed it from &lt;code&gt;file()&lt;/code&gt; or &lt;code&gt;templatefile()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The instance’s &lt;code&gt;ipv4&lt;/code&gt; attribute will include the static address once cloud-init finishes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more complex cloud-init content, the provider also supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cloud_init_file&lt;/code&gt;&lt;/strong&gt;: path to a YAML file on disk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cloud_init&lt;/code&gt;&lt;/strong&gt;: inline YAML (mutually exclusive with &lt;code&gt;cloud_init_file&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Template-driven cloud-init (CI runner example)
&lt;/h2&gt;

&lt;p&gt;The provider ships with a &lt;code&gt;cloud-init-lab&lt;/code&gt; example that shows how to render cloud-init from a template and assign it to instances.&lt;/p&gt;

&lt;p&gt;A simplified pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rendered_cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/cloud-init.tpl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ci-runner"&lt;/span&gt;
    &lt;span class="nx"&gt;motd&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Runner ready!"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_instance"&lt;/span&gt; &lt;span class="s2"&gt;"runner"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ci-runner"&lt;/span&gt;
  &lt;span class="nx"&gt;image&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"lts"&lt;/span&gt;
  &lt;span class="nx"&gt;cpus&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
  &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"3G"&lt;/span&gt;
  &lt;span class="nx"&gt;disk&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"12G"&lt;/span&gt;

  &lt;span class="nx"&gt;cloud_init&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rendered_cloud_init&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Template (&lt;code&gt;cloud-init.tpl&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#cloud-config&lt;/span&gt;
&lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${username}&lt;/span&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sudo&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/bin/bash&lt;/span&gt;
    &lt;span class="na"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALL=(ALL)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;NOPASSWD:ALL"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;runcmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;printf "%s\n" "${motd}" &amp;gt; /etc/motd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Per-environment&lt;/strong&gt; cloud-init (dev vs staging vs CI).&lt;/li&gt;
&lt;li&gt;Injecting SSH keys, user accounts, packages, and systemd unit files.&lt;/li&gt;
&lt;li&gt;Staying purely declarative (no provisioners required).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  File upload and download without provisioners
&lt;/h2&gt;

&lt;p&gt;Instead of relying on &lt;code&gt;local-exec&lt;/code&gt; + &lt;code&gt;multipass transfer&lt;/code&gt;, the provider exposes dedicated resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;multipass_file_upload&lt;/code&gt;&lt;/strong&gt;: host → instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;multipass_file_download&lt;/code&gt;&lt;/strong&gt;: instance → host.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example: upload a rendered config and fetch logs back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_file_upload"&lt;/span&gt; &lt;span class="s2"&gt;"nginx_conf"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"upload"&lt;/span&gt;

  &lt;span class="nx"&gt;source_path&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/nginx.conf"&lt;/span&gt;
  &lt;span class="nx"&gt;destination_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/etc/nginx/nginx.conf"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_file_download"&lt;/span&gt; &lt;span class="s2"&gt;"logs"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;instance&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;direction&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"download"&lt;/span&gt;

  &lt;span class="nx"&gt;source_path&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/var/log/nginx"&lt;/span&gt;
  &lt;span class="nx"&gt;destination_path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${path.module}/nginx-logs"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Integrate with Terraform’s dependency graph.&lt;/li&gt;
&lt;li&gt;Are idempotent (only rerun when inputs change).&lt;/li&gt;
&lt;li&gt;Remove the need for ad-hoc shell provisioners.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Ansible-friendly: stable IPs and inventories
&lt;/h2&gt;

&lt;p&gt;If you’re driving configuration with Ansible (similar to the workflow described in &lt;a href="https://ryan-schachte.com/blog/ansible_multipass/" rel="noopener noreferrer"&gt;Ryan Schachte’s blog post on Multipass, Terraform &amp;amp; Ansible&lt;/a&gt;), you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;networks { mac = "…" }&lt;/code&gt; + cloud-init Netplan to get &lt;strong&gt;stable IPs&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Expose those IPs via &lt;code&gt;output&lt;/code&gt;s or data sources.&lt;/li&gt;
&lt;li&gt;Generate inventory files using &lt;code&gt;local_file&lt;/code&gt; or other Terraform tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example of an inventory-like output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"ansible_hosts"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;master&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lab_master&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;worker1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lab_worker1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;worker2&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lab_worker2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ipv4&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, you can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Point Ansible directly at those outputs (e.g. via &lt;code&gt;terraform output -json&lt;/code&gt;), or&lt;/li&gt;
&lt;li&gt;Render an INI inventory file with &lt;code&gt;local_file&lt;/code&gt; and &lt;code&gt;templatefile&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Examples&lt;/strong&gt;: explore the &lt;code&gt;examples&lt;/code&gt; directory (&lt;code&gt;basic&lt;/code&gt;, &lt;code&gt;dev-lab&lt;/code&gt;, &lt;code&gt;bridged-workstation&lt;/code&gt;, &lt;code&gt;cloud-init-lab&lt;/code&gt;) for ready-made scenarios.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs&lt;/strong&gt;: check the &lt;code&gt;multipass_instance&lt;/code&gt; resource docs for full argument and attribute references, including &lt;code&gt;cloud_init&lt;/code&gt;, &lt;code&gt;networks&lt;/code&gt;, and &lt;code&gt;mounts&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI / CD&lt;/strong&gt;: plug this provider into your existing Terraform flows to bootstrap ephemeral CI runners, dev boxes, or disposable labs on top of Multipass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With &lt;strong&gt;multiple NICs&lt;/strong&gt;, &lt;strong&gt;static IPs via cloud-init&lt;/strong&gt;, &lt;strong&gt;file transfer resources&lt;/strong&gt;, and &lt;strong&gt;inline cloud-init&lt;/strong&gt; all first-class, this provider is designed to make Multipass feel like a natural part of your Terraform-based infrastructure story.&lt;/p&gt;

</description>
      <category>devops</category>
      <category>automation</category>
      <category>terraform</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
