Kevin McMahon

Terraform for Your Always Free Claude Code VM

In my previous post, I walked through setting up a zero-cost mobile Claude Code environment on Oracle Cloud’s Always Free tier. The VM, VCN, subnet, security list, and internet gateway take maybe 15 minutes to click through in the Oracle console, and you are up and running.

This is the type of thing that you might do once and never think about it again. Unless you blow it away. Or want a second one. Or want a friend to replicate your setup. Then you’re clicking through that console again, trying to remember which shape you picked and which security rules you set. Infrastructure-as-code exists precisely for this kind of thing.

For those cases, I put the whole stack in Terraform.

What It Provisions

The stack creates everything from Parts 2-3 of the setup guide:

  • Compute instance — VM.Standard.A1.Flex (ARM), 4 OCPUs, 24 GB RAM, Ubuntu 24.04
  • VCN with a public subnet
  • Internet gateway and route table
  • Security list — SSH ingress on port 22 (for initial setup, before Tailscale)

After terraform apply, you SSH in, install Tailscale, lock down the security list, and continue with the rest of the guide. The Terraform part replaces the manual console work; the post-provisioning steps (Tailscale, mosh, tmux, Claude Code, ntfy) stay the same.

Two Variables, That’s It

The whole configuration boils down to two required variables:

Variable What it is
compartment_id Your OCI compartment OCID (usually your tenancy OCID)
region OCI region, e.g. us-chicago-1

Everything else has sensible defaults. The availability domain and Ubuntu image are auto-discovered via OCI data sources, so you don’t need to hunt down region-specific OCIDs.

Before you start, make sure your OCI CLI is installed and your credentials are set up.

Run:

oci iam region-list

to verify your profile is active and authenticated. This helps avoid credential errors later.

git clone https://github.com/kevinmcmahon/oci-alwaysfree-tf-stack.git
cd oci-alwaysfree-tf-stack

cp terraform.tfvars.example terraform.tfvars
# edit terraform.tfvars: set compartment_id and region

terraform init
terraform plan
terraform apply

Your VM is running. SSH in and pick up at Part 3 of the setup guide:

ssh -i ~/.ssh/your-key ubuntu@$(terraform output -raw instance_public_ip)

Staying Inside the Free Limits

The defaults are tuned to use the full Always Free ARM allowance without exceeding it:

Resource Stack default Always Free limit
ARM OCPUs 4 4 total
Memory 24 GB 24 GB total
Boot volume 47 GB 200 GB total
Instances 1 Up to 4 ARM + 2 AMD

If you change the defaults — say, bumping the boot volume to 200 GB — just make sure the total across all your instances stays within limits. And set up budget alerts at $0.01 actual spend so you’ll know immediately if anything crosses the free line.

Overridable Defaults

You probably won’t need to touch these, but they’re there:

Variable Default Why you’d change it
availability_domain_index 0 Your preferred AD is full — try 1 or 2
ssh_public_key_path ~/.ssh/id_ed25519.pub You use a different key
instance_name claude-dev You want a different display name
ocpus 4 You’re splitting free resources across multiple instances
memory_in_gbs 24 Same — splitting across instances
boot_volume_size_in_gbs 47 You need more disk (up to 200 GB free total)
assign_public_ip true Set to false after Tailscale is running

Tearing Down and Rebuilding

This is the whole point of putting it in Terraform:

terraform destroy   # gone
terraform apply     # back

State tracks what was created, so destroy is clean. No orphaned resources, no mystery charges showing up a month later because you forgot to delete a route table.

The rebuild cycle is useful when Oracle releases new Ubuntu images, when you want to start fresh after experimenting, or when you want to help someone else set up their own instance. Fork the repo, set two variables, apply.

After Terraform: The Rest of the Stack

Terraform gets you the VM. Everything after that — Tailscale, UFW, mosh, tmux, Claude Code, ntfy hooks — is covered in the full setup guide. The short version:

  1. SSH in via the public IP
  2. Install Tailscale, authenticate, note your Tailscale IP
  3. Configure UFW to allow only the tailscale0 interface
  4. Lock down the Oracle security list (remove SSH ingress)
  5. Optionally, terraform apply again with assign_public_ip = false to drop the public IP
  6. Install mosh, tmux, Node.js, Claude Code
  7. Set up ntfy notifications

From that point on, you connect via Termius on your phone, mosh over Tailscale, and Claude Code runs in a persistent tmux session. Push notifications tell you when Claude needs input. The VM runs 24/7 for free.