<?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>DEV Community: todoroff</title>
    <description>The latest articles on DEV Community by todoroff (@todoroff).</description>
    <link>https://dev.to/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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3617257%2F993b6574-6c4e-43e5-ba61-cd7ddfa29f00.png</url>
      <title>DEV Community: todoroff</title>
      <link>https://dev.to/todoroff</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/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://dev.to/todoroff/terraform-for-local-vms-a-modern-alternative-to-vagrant-3j23</link>
      <guid>https://dev.to/todoroff/terraform-for-local-vms-a-modern-alternative-to-vagrant-3j23</guid>
      <description>&lt;p&gt;&lt;em&gt;Updated October 2026 for provider 2.0.1.&lt;/em&gt;&lt;/p&gt;

&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-specific setup&lt;/strong&gt;&lt;br&gt;
Vagrant supports &lt;a href="https://developer.hashicorp.com/vagrant/docs/providers" rel="noopener noreferrer"&gt;several providers&lt;/a&gt;, including Hyper-V. Each backend still comes with its own setup and configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;📦 Heavy box downloads&lt;/strong&gt;&lt;br&gt;
The first use of a new box meant a large image download.&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="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"~&amp;gt; 2.0.1"&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;wait_for_cloud_init&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;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:
      - 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 (Multipass downloads and caches the selected image)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both install Nginx. The Multipass example uses its default network; matching Vagrant's static private IP needs additional network configuration.&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;p&gt;You'll need Terraform 1.6+ and Multipass 1.13+ on the same host. The provider calls the local Multipass CLI.&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;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;multipass

&lt;span class="c"&gt;# Ubuntu/Debian (with snapd installed)&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;# Check that Multipass is 1.13 or newer&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; 2.0.1"&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 600s)&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="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. This example provisions PostgreSQL and Nginx, and passes the database address to the web VM. A real application still needs its database connection and authentication configured.&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 "&amp;lt;h1&amp;gt;Web server&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;Database: 192.168.56.10&amp;lt;/p&amp;gt;" &amp;gt; /var/www/html/index.html
&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;p&gt;Use a new directory for this example, with &lt;code&gt;main.tf&lt;/code&gt; and &lt;code&gt;web-init.yaml&lt;/code&gt; together.&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;# 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; 2.0.1"&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;wait_for_cloud_init&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;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="nx"&gt;timeouts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20m"&lt;/span&gt;
  &lt;span class="p"&gt;}&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;wait_for_cloud_init&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;timeouts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20m"&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;#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;/var/www/html/index.html&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0644'&lt;/span&gt;
    &lt;span class="na"&gt;defer&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;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;&amp;lt;h1&amp;gt;Web server&amp;lt;/h1&amp;gt;&lt;/span&gt;
      &lt;span class="s"&gt;&amp;lt;p&amp;gt;Database: ${db_host}&amp;lt;/p&amp;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;p&gt;Save the YAML as &lt;code&gt;web-init.yaml&lt;/code&gt;. The DB reference creates Terraform's dependency automatically. &lt;code&gt;wait_for_cloud_init = true&lt;/code&gt; also waits for guest provisioning to finish; &lt;code&gt;depends_on&lt;/code&gt; alone would not do that. The page displays the DB address to demonstrate templating.&lt;/p&gt;




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

&lt;h3&gt;
  
  
  Resize an Existing VM
&lt;/h3&gt;

&lt;p&gt;Provider 2.x defaults to &lt;code&gt;resize_policy = "in_place"&lt;/code&gt;: CPU and memory changes, and disk growth, resize the existing VM. Running VMs stop and restart; already stopped VMs stay stopped. Disk shrink is rejected before mutation, and guest filesystem expansion may require a separate step.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;resize_policy = "replace"&lt;/code&gt; to keep the previous behavior of rebuilding on size changes. Name, image, cloud-init, and network changes still force recreation. Changing the policy alone leaves the VM as it is.&lt;/p&gt;

&lt;p&gt;When upgrading from 1.x, change the version constraint and run &lt;code&gt;terraform init -upgrade&lt;/code&gt;, then review the plan. &lt;code&gt;~&amp;gt; 1.5&lt;/code&gt; excludes 2.x. See the &lt;a href="https://github.com/todoroff/terraform-provider-multipass/releases/tag/2.0.0" rel="noopener noreferrer"&gt;2.0 upgrade notes&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;Multipass has &lt;strong&gt;native snapshots&lt;/strong&gt;, which the provider can manage. Vagrant also supports &lt;a href="https://developer.hashicorp.com/vagrant/docs/cli/snapshot" rel="noopener noreferrer"&gt;snapshots&lt;/a&gt; with compatible backends.&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the instance configuration first. Then stop the VM before adding the snapshot resource:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass stop test-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&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;Apply again to create the snapshot, then run &lt;code&gt;multipass start test-env&lt;/code&gt;. A snapshot requires a stopped instance; the resource does not stop it automatically.&lt;/p&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;Terraform tracks the snapshot. To restore it, use the CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass stop test-env
multipass restore test-env.pre-upgrade-backup
multipass start test-env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the restore prompt, then review &lt;code&gt;terraform plan&lt;/code&gt; because restoration can change the VM relative to your configuration.&lt;/p&gt;

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

&lt;p&gt;For config files and log downloads, the provider has dedicated transfer resources. Create &lt;code&gt;configs/app.yaml&lt;/code&gt; locally before applying this example:&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;"/home/ubuntu/app.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/nginx/access.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="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;refresh&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="c1"&gt;# Change this value to download a fresh copy.&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;Uploads need a writable destination; the transfer does not elevate permissions for paths under &lt;code&gt;/etc&lt;/code&gt;. Downloads need readable source files. Transfers are discrete copies, and remote edits do not automatically trigger another transfer. Destroying these resources removes their respective remote or local destination.&lt;/p&gt;

&lt;p&gt;For a shared source directory, use a &lt;code&gt;mounts&lt;/code&gt; block inside your instance resource:&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;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="nx"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${path.module}/src"&lt;/span&gt;&lt;span class="p"&gt;)&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;src&lt;/code&gt; on the host before applying. This is the closer equivalent to &lt;code&gt;config.vm.synced_folder&lt;/code&gt;; mounts can be added or removed without recreating the VM.&lt;/p&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;multipass db-psql      &lt;span class="c"&gt;# Opens psql on db server&lt;/span&gt;
multipass 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;For the shorter &lt;code&gt;db-psql&lt;/code&gt; and &lt;code&gt;web-logs&lt;/code&gt; commands, add the Multipass alias directory to your host's &lt;code&gt;PATH&lt;/code&gt; as described in the &lt;a href="https://canonical.com/multipass/docs/latest/how-to-guides/manage-instances/use-instance-command-aliases/" rel="noopener noreferrer"&gt;alias guide&lt;/a&gt;.&lt;/p&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; to create; &lt;code&gt;multipass start &amp;lt;name&amp;gt;&lt;/code&gt; to start an existing stopped VM&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;multipass restart &amp;lt;name&amp;gt;&lt;/code&gt; to reboot; &lt;code&gt;terraform apply&lt;/code&gt; for configuration 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; at creation; changing it recreates the VM&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;mounts { }&lt;/code&gt; block&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; for host interfaces; configure static IPs inside the guest&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 rely on a particular Vagrant provider or plugin&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&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 a quickstart. The first image download and package installation can take a few minutes.&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;p&gt;The following file-creation commands use Bash. In PowerShell, save the HCL between &lt;code&gt;EOF&lt;/code&gt; markers as &lt;code&gt;main.tf&lt;/code&gt; using your editor.&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="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"
      version = "~&amp;gt; 2.0.1"
    }
  }
}

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

  wait_for_cloud_init = true

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

  timeouts {
    create = "20m"
  }
}

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://dev.to/todoroff/build-a-local-kubernetes-cluster-in-minutes-with-terraform-and-multipass-14e4</link>
      <guid>https://dev.to/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://dev.to/todoroff/multipass-terraform-modern-vm-automation-guide-129l</link>
      <guid>https://dev.to/todoroff/multipass-terraform-modern-vm-automation-guide-129l</guid>
      <description>&lt;h1&gt;
  
  
  Multipass + Terraform: Modern VM Automation Guide
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;Updated for &lt;code&gt;todoroff/multipass&lt;/code&gt; 2.0.1, October 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://canonical.com/multipass" rel="noopener noreferrer"&gt;Multipass&lt;/a&gt; creates Ubuntu virtual machines on your workstation. The &lt;a href="https://registry.terraform.io/providers/todoroff/multipass/latest/docs" rel="noopener noreferrer"&gt;&lt;code&gt;todoroff/multipass&lt;/code&gt; Terraform provider&lt;/a&gt; lets you describe those machines in code, along with their networks, host mounts, aliases, snapshots, and file transfers.&lt;/p&gt;

&lt;p&gt;This guide starts with one VM and then covers resizing, cloud-init, multiple network interfaces, static addresses, and configuration workflows with Ansible or a CI system.&lt;/p&gt;

&lt;p&gt;The provider runs the &lt;code&gt;multipass&lt;/code&gt; CLI on the machine running Terraform. That machine needs Multipass installed and access to its daemon; a hosted Terraform runner does not automatically have access to the VMs on your laptop.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install the prerequisites
&lt;/h2&gt;

&lt;p&gt;You need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform 1.6 or newer.&lt;/li&gt;
&lt;li&gt;Multipass 1.13 or newer, installed and available on &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A working Multipass driver for your operating system. Networking, snapshots, and resizing also depend on the capabilities of that driver.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check that the CLI can reach the daemon:&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="nt"&gt;--format&lt;/span&gt; json
multipass list &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go is only needed when building the provider from source.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Create a VM and a shell alias
&lt;/h2&gt;

&lt;p&gt;Create an empty directory and save the following as &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_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.6.0"&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; 2.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="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;"24.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;"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="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_ips"&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;p&gt;Then initialize, review, and apply the configuration:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The alias opens Bash inside the VM. The &lt;code&gt;ipv4&lt;/code&gt; attribute contains the addresses reported by Multipass.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;~&amp;gt; 2.0&lt;/code&gt; permits provider versions from 2.0.0 up to, but excluding, 3.0.0. Commit &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; to retain the selected version across runs. Use &lt;code&gt;terraform init -upgrade&lt;/code&gt; when you intentionally want to update within that constraint.&lt;/p&gt;

&lt;p&gt;This example selects the Ubuntu &lt;code&gt;24.04&lt;/code&gt; release. You can use &lt;code&gt;image = "lts"&lt;/code&gt; to follow Multipass's current LTS alias for new launches. An image alias does not pin an exact image build or upgrade an existing guest operating system.&lt;/p&gt;

&lt;p&gt;The empty provider block uses these defaults:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;multipass_path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;multipass&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Find the CLI on &lt;code&gt;PATH&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;command_timeout&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;600&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timeout in seconds for CLI operations.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;default_image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;lts&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image used when an instance omits &lt;code&gt;image&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Set &lt;code&gt;multipass_path&lt;/code&gt; only if your binary is elsewhere. Paths differ between operating systems and installation methods.&lt;/p&gt;

&lt;p&gt;The remaining sections are optional additions or edits to this configuration. When an example says to replace a resource block, keep only one block with that resource type and Terraform name.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Resize an existing VM
&lt;/h2&gt;

&lt;p&gt;Provider 2.0 changed the default behavior of allocation updates. CPU and memory changes, and disk growth, now resize the existing VM.&lt;/p&gt;

&lt;p&gt;For example, replace the earlier &lt;code&gt;multipass_instance.dev&lt;/code&gt; block with:&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;"24.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;4&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;"8G"&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;"30G"&lt;/span&gt;
  &lt;span class="nx"&gt;resize_policy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"in_place"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;terraform plan&lt;/code&gt; and &lt;code&gt;terraform apply&lt;/code&gt; again.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Change&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;in_place&lt;/code&gt; (default)&lt;/th&gt;
&lt;th&gt;&lt;code&gt;replace&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU or memory allocation&lt;/td&gt;
&lt;td&gt;Resize the existing VM&lt;/td&gt;
&lt;td&gt;Recreate the VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk growth&lt;/td&gt;
&lt;td&gt;Expand the existing disk&lt;/td&gt;
&lt;td&gt;Recreate the VM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disk shrink&lt;/td&gt;
&lt;td&gt;Reject during apply, before mutation&lt;/td&gt;
&lt;td&gt;Recreate with a smaller disk&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A running VM is gracefully stopped and restarted for an in-place resize, so expect downtime. A stopped VM remains stopped. Start or stop a suspended or transitional VM before resizing it.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;resize_policy = "replace"&lt;/code&gt; when you want a fresh VM after allocation changes. Changing only the policy does not resize, restart, or replace the VM. The provider never silently switches a failed in-place resize to replacement.&lt;/p&gt;

&lt;p&gt;Removing &lt;code&gt;cpus&lt;/code&gt;, &lt;code&gt;memory&lt;/code&gt;, or &lt;code&gt;disk&lt;/code&gt; from an existing resource keeps its current allocation. New instances that omit them default to 1 CPU, 1G memory, and 5G disk. Equivalent sizes such as &lt;code&gt;1G&lt;/code&gt; and &lt;code&gt;1024M&lt;/code&gt; do not trigger a resize.&lt;/p&gt;

&lt;p&gt;Increasing the virtual disk does not guarantee that the guest partition and filesystem expand with it. Check guest capacity after growth and follow &lt;a href="https://canonical.com/multipass/docs/latest/how-to-guides/manage-instances/modify-an-instance/" rel="noopener noreferrer"&gt;Multipass's instance modification guide&lt;/a&gt; if another step is needed.&lt;/p&gt;

&lt;p&gt;Name, image, network, and cloud-init changes still force replacement, regardless of &lt;code&gt;resize_policy&lt;/code&gt;. Review the plan before applying those changes. See the &lt;a href="https://registry.terraform.io/providers/todoroff/multipass/latest/docs/resources/instance" rel="noopener noreferrer"&gt;instance documentation&lt;/a&gt; for resize deadlines, recovery, and allocation readback limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Share a host directory
&lt;/h2&gt;

&lt;p&gt;To mount a project directory, add this block &lt;strong&gt;inside&lt;/strong&gt; your existing &lt;code&gt;multipass_instance.dev&lt;/code&gt; resource. Replace the host path with an existing directory on your machine:&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;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/your-user/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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On Windows, an HCL path can use forward slashes, such as &lt;code&gt;C:/Users/YourName/projects&lt;/code&gt;. On macOS, it might be &lt;code&gt;/Users/your-name/projects&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Mounts can be added and removed without recreating the VM. Leave &lt;code&gt;read_only&lt;/code&gt; omitted or set it to &lt;code&gt;false&lt;/code&gt;: the Multipass CLI does not support read-only mounts, and the provider rejects &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Bootstrap with cloud-init
&lt;/h2&gt;

&lt;p&gt;Use cloud-init for packages, users, and configuration that must exist when a VM is first created. There are two mutually exclusive inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;cloud_init_file&lt;/code&gt;: a path to a cloud-init YAML file.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cloud_init&lt;/code&gt;: YAML supplied as a Terraform string, including the result of &lt;code&gt;file()&lt;/code&gt; or &lt;code&gt;templatefile()&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a templated example, save this as &lt;code&gt;cloud-init.yaml.tftpl&lt;/code&gt; next to &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 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="s"&gt;default&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;${jsonencode(username)}&lt;/span&gt;
    &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;]&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;ssh_authorized_keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;${jsonencode(ssh_public_key)}&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;git&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/motd&lt;/span&gt;
    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0644"&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${jsonencode(motd)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;default&lt;/code&gt; entry preserves the image's default user alongside the new account. &lt;code&gt;jsonencode()&lt;/code&gt; produces quoted strings that are also valid YAML, including when a value contains quotes or line breaks.&lt;/p&gt;

&lt;p&gt;Add the following to &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;variable&lt;/span&gt; &lt;span class="s2"&gt;"ssh_public_key"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Your SSH public key, used for guest access."&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;"24.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;"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;"15G"&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}/cloud-init.yaml.tftpl"&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;ssh_public_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ssh_public_key&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;"CI host ready.&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;n"&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="nx"&gt;wait_for_cloud_init&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;timeouts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20m"&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;Set &lt;code&gt;ssh_public_key&lt;/code&gt; in &lt;code&gt;terraform.tfvars&lt;/code&gt; to the contents of your &lt;code&gt;.pub&lt;/code&gt; file. Keep the private key on your host. This example creates the account and installs Git; installing and registering your CI agent is a separate configuration step.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;wait_for_cloud_init = true&lt;/code&gt; makes Terraform wait for cloud-init before reporting successful creation. Use it when later resources depend on packages, files, or networking configured during bootstrap. If cloud-init fails or times out, creation reports an error and dependent resources do not run; the created VM remains recorded in Terraform state.&lt;/p&gt;

&lt;p&gt;Cloud-init configures a new VM. Changing inline or rendered cloud-init forces replacement. With &lt;code&gt;cloud_init_file&lt;/code&gt;, changing the path forces replacement, but editing the file at the same path does not itself change that Terraform argument. Use &lt;code&gt;cloud_init = file(...)&lt;/code&gt; when file-content changes should appear in the plan.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Add network interfaces
&lt;/h2&gt;

&lt;p&gt;Start by finding networks on your own host:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass networks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also expose the list through Terraform:&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;data&lt;/span&gt; &lt;span class="s2"&gt;"multipass_networks"&lt;/span&gt; &lt;span class="s2"&gt;"host"&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;"host_networks"&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;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multipass_networks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;networks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each &lt;code&gt;networks&lt;/code&gt; block becomes a &lt;code&gt;multipass launch --network&lt;/code&gt; argument. Additional interfaces are attached alongside the default interface that Multipass uses to communicate with the VM.&lt;/p&gt;

&lt;p&gt;For a VM with two extra interfaces, add:&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;variable&lt;/span&gt; &lt;span class="s2"&gt;"lan_network"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A usable host network name from multipass networks."&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"second_network"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"A second usable host network name."&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;"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;"24.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;"10G"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lan_network&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;"auto"&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:01"&lt;/span&gt;
  &lt;span class="p"&gt;}&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;second_network&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;"auto"&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:02"&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;Set the network variables in &lt;code&gt;terraform.tfvars&lt;/code&gt; using names available on your host. Use a unique MAC for each interface on a network. A fixed MAC is useful for a DHCP reservation; &lt;code&gt;mode = "auto"&lt;/code&gt; lets Multipass configure the interface automatically. Manual mode requires guest-side configuration.&lt;/p&gt;

&lt;p&gt;Attaching two interfaces does not enable IP forwarding or configure routing between them. That is a separate guest configuration step if you want this machine to act as a router.&lt;/p&gt;

&lt;p&gt;Networking support and bridge setup depend on your operating system and Multipass driver. Prepare any required host bridge before an unattended Terraform run. See &lt;a href="https://canonical.com/multipass/docs/latest/how-to-guides/manage-instances/set-up-custom-networking/" rel="noopener noreferrer"&gt;Multipass's custom networking guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Changing a resource's &lt;code&gt;networks&lt;/code&gt; blocks replaces that VM. Decide on its network layout before storing data in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Configure a static address
&lt;/h2&gt;

&lt;p&gt;A static address combines a fixed MAC, manual networking, and guest configuration. Choose an unused address on the subnet of the selected host network, outside its dynamic DHCP pool or reserved appropriately. The address below is an example; change it to match your network.&lt;/p&gt;

&lt;p&gt;This example reuses &lt;code&gt;lan_network&lt;/code&gt; and &lt;code&gt;ssh_public_key&lt;/code&gt; from the previous sections. If using it on its own, copy those variable declarations along with the provider 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;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;static_cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"192.168.1.210/24"&lt;/span&gt;
  &lt;span class="nx"&gt;static_ip&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/"&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;static_cidr&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;static_mac&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"52:54:00:4b:ab:03"&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;"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;"24.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;"10G"&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="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lan_network&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="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_mac&lt;/span&gt;
  &lt;span class="p"&gt;}&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
    ssh_authorized_keys:
      - ${jsonencode(var.ssh_public_key)}
    write_files:
      - path: /etc/netplan/10-custom.yaml
        permissions: "0600"
        content: |
          network:
            version: 2
            ethernets:
              extra0:
                match:
                  macaddress: "${local.static_mac}"
                dhcp4: false
                addresses: ["${local.static_cidr}"]
    runcmd:
      - [netplan, apply]
&lt;/span&gt;&lt;span class="no"&gt;  EOT

&lt;/span&gt;  &lt;span class="nx"&gt;wait_for_cloud_init&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;timeouts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"20m"&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;"static_ip"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"The address configured for the additional interface."&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;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;static_ip&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;"reported_ips"&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;p&gt;The default interface remains available for Multipass and outbound connectivity. The extra interface gets the static address. For an isolated lab network, create a host bridge on its own subnet first; Canonical's &lt;a href="https://canonical.com/multipass/docs/latest/how-to-guides/manage-instances/configure-static-ips/" rel="noopener noreferrer"&gt;static-IP guide&lt;/a&gt; shows that approach.&lt;/p&gt;

&lt;p&gt;Waiting for cloud-init ensures the Netplan command completes before the provider reads the final instance information. It does not prove the address is reachable from every machine on your network.&lt;/p&gt;

&lt;p&gt;Do not assume &lt;code&gt;ipv4[0]&lt;/code&gt; is the static address. The provider returns Multipass's reported addresses without selecting one by interface. Here, &lt;code&gt;static_ip&lt;/code&gt; is the configured value, while &lt;code&gt;reported_ips&lt;/code&gt; shows what Multipass observed.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Upload and download files
&lt;/h2&gt;

&lt;p&gt;The provider has separate resources for uploads and downloads. Both use &lt;code&gt;source&lt;/code&gt; and &lt;code&gt;destination&lt;/code&gt;; the resource type determines the direction.&lt;/p&gt;

&lt;p&gt;For a complete round trip, add this example using the original &lt;code&gt;dev&lt;/code&gt; VM:&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;"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;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;destination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"/home/ubuntu/demo/app.conf"&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"PORT=8080&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;nLOG_LEVEL=info&lt;/span&gt;&lt;span class="err"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;n"&lt;/span&gt;

  &lt;span class="nx"&gt;create_parents&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;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&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;dev&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_file_download"&lt;/span&gt; &lt;span class="s2"&gt;"config_copy"&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;source&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_file_upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;destination&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}/downloads/app.conf"&lt;/span&gt;
  &lt;span class="nx"&gt;create_parents&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;overwrite&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;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;multipass_file_upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content_hash&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;replace_triggered_by&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_file_upload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&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;The upload uses a path writable by the default Ubuntu user. Referencing the upload's destination makes the download wait for it. The hash trigger refreshes the local copy when the payload changes.&lt;/p&gt;

&lt;p&gt;The lifecycle rules also repeat the transfers after a planned VM update or replacement. Multipass instance IDs are names, so a rebuilt VM can have the same ID string. Referencing the whole resource in &lt;code&gt;replace_triggered_by&lt;/code&gt; follows its planned updates and replacements, including those where its name stays the same. See Terraform's &lt;a href="https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#replace_triggered_by" rel="noopener noreferrer"&gt;lifecycle reference&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To upload an existing local file, replace &lt;code&gt;content&lt;/code&gt; with &lt;code&gt;source = "${path.module}/files/app.conf"&lt;/code&gt; and create that file before planning. Provide exactly one of &lt;code&gt;source&lt;/code&gt; or &lt;code&gt;content&lt;/code&gt;. For directories, use &lt;code&gt;recursive = true&lt;/code&gt; on both the upload and download resources and give each resource a dedicated destination directory.&lt;/p&gt;

&lt;p&gt;Transfers do not run with automatic &lt;code&gt;sudo&lt;/code&gt;. Use cloud-init to write files under privileged paths such as &lt;code&gt;/etc/nginx&lt;/code&gt;, or explicitly arrange a separate privileged installation step. Uploading a config file also does not install or restart its application.&lt;/p&gt;

&lt;h3&gt;
  
  
  What file resources track
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Uploads hash the local payload and transfer it when that hash changes. Guest-side edits to the uploaded file are not detected.&lt;/li&gt;
&lt;li&gt;Downloads do not continuously watch the guest file. Change &lt;code&gt;triggers&lt;/code&gt; or request replacement when you want a fresh copy.&lt;/li&gt;
&lt;li&gt;Destroying an upload removes its remote destination. Destroying a download removes its local managed payload. A recursive download owns its destination directory, so keep unrelated files elsewhere.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example, to fetch the current remote copy explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-replace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;multipass_file_download.config_copy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use these resources for files whose creation and deletion should follow Terraform's lifecycle. Choose a separate collection process for logs or backups that must outlive &lt;code&gt;terraform destroy&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Use the address with Ansible
&lt;/h2&gt;

&lt;p&gt;For the static-IP example, this output exposes the intended address and guest user without relying on address-list ordering:&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;lab_node_1&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ansible_host&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;static_ip&lt;/span&gt;
      &lt;span class="nx"&gt;ansible_user&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"ubuntu"&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;Read it with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform output &lt;span class="nt"&gt;-json&lt;/span&gt; ansible_hosts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is input for an inventory generator, not a complete Ansible dynamic inventory. Render your preferred inventory format, or adapt the JSON to Ansible's inventory schema.&lt;/p&gt;

&lt;p&gt;The static-IP example installs your public key for the default Ubuntu user. Ansible still needs the matching private key and network access from the machine where it runs. Terraform's &lt;code&gt;wait_for_cloud_init&lt;/code&gt; covers bootstrap completion; use Ansible's connection waiting when your workflow also needs to account for SSH becoming reachable.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Manage snapshots and existing instances
&lt;/h2&gt;

&lt;p&gt;The provider can manage named snapshots, but the target VM must be stopped. A dependency on the instance only establishes ordering; it does not stop it.&lt;/p&gt;

&lt;p&gt;To snapshot the existing &lt;code&gt;dev-box&lt;/code&gt;, stop it first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass stop dev-box
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then add this resource and apply the 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;resource&lt;/span&gt; &lt;span class="s2"&gt;"multipass_snapshot"&lt;/span&gt; &lt;span class="s2"&gt;"backup"&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;name&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pre-upgrade"&lt;/span&gt;
  &lt;span class="nx"&gt;comment&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Before upgrading the development environment"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run this step separately from pending file-transfer operations that need a running guest. Start the VM again when the snapshot has completed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;multipass start dev-box
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Existing objects can also be imported. Add the corresponding resource configuration first, then import it instead of creating it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform import multipass_instance.dev dev-box
terraform import multipass_alias.shell dev-shell
terraform import multipass_snapshot.backup dev-box.pre-upgrade
terraform import multipass_file_upload.config dev-box:/home/ubuntu/demo/app.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are alternative commands for objects not already managed at those Terraform addresses. Review the next plan after import. File downloads cannot be imported.&lt;/p&gt;

&lt;p&gt;For inspection without lifecycle ownership, use the &lt;code&gt;multipass_instance&lt;/code&gt; data source. Other data sources list available images, host networks, and snapshots. See the &lt;a href="https://registry.terraform.io/providers/todoroff/multipass/latest/docs" rel="noopener noreferrer"&gt;provider documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Recovery and cleanup
&lt;/h2&gt;

&lt;p&gt;For an instance that might be soft-deleted outside Terraform, add &lt;code&gt;auto_recover = true&lt;/code&gt; to its resource. Add &lt;code&gt;auto_start_on_recover = true&lt;/code&gt; if it should also be started after recovery. Recovery does not recreate a purged VM or restore files from a backup.&lt;/p&gt;

&lt;p&gt;When the lab is no longer needed:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Review everything managed in that configuration, including VM disks, snapshots, aliases, uploaded files, and downloaded copies. Keep data that must survive teardown outside those managed destinations.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Browse the &lt;a href="https://github.com/todoroff/terraform-provider-multipass/tree/2.0.1/examples" rel="noopener noreferrer"&gt;complete examples&lt;/a&gt; for development labs, host mounts, cloud-init, and file workflows.&lt;/li&gt;
&lt;li&gt;Read the &lt;a href="https://github.com/todoroff/terraform-provider-multipass/releases/tag/2.0.0" rel="noopener noreferrer"&gt;2.0 upgrade notes&lt;/a&gt; before moving an existing 1.x configuration to the new resize default.&lt;/li&gt;
&lt;li&gt;Prefer TypeScript or Python? The provider also works through Pulumi's Any Terraform Provider bridge. Start with the &lt;a href="https://github.com/todoroff/terraform-provider-multipass/blob/2.0.1/docs/guides/pulumi.md" rel="noopener noreferrer"&gt;Pulumi guide&lt;/a&gt; and its language examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Start with a small VM, add only the networking and bootstrap configuration you need, and review each plan before applying it. The same resource model then scales to repeatable development environments, test labs, and local CI hosts.&lt;/p&gt;

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