Terraform stack that provisions an Always Free ARM instance on Oracle Cloud, with VCN, subnet, and internet gateway. Auto-discovers the availability domain and latest Ubuntu 24.04 image so you don't need to look up region-specific OCIDs.
- 1 VCN
- 1 Public Subnet
- 1 Internet Gateway
- Up to 4 ARM Compute Instances
- Up to 24GB Memory
- Up to 200GB Block Storage
- An active OCI account (sign up for Always Free).
- Terraform installed.
- OCI CLI installed and configured with API keys — see Oracle's Required Keys and OCIDs guide for the full walkthrough. Quick version:
uv tool install oci-cli # or: pipx install oci-cli oci setup config # generates ~/.oci/config + API key pair # Then upload ~/.oci/oci_api_key_public.pem to: # OCI Console → Profile → My profile → API keys → Add API key
- An SSH key pair (
ssh-keygen -t ed25519if you don't have one).
-
Copy the example variables file:
cp terraform.tfvars.example terraform.tfvars
-
Edit
terraform.tfvars— only two values are required:# Find your compartment (tenancy) OCID: oci iam compartment list --query 'data[0]."compartment-id"' --raw-output # List available regions: oci iam region list --query 'data[*].name' --raw-output
-
Run the preflight check to catch config issues before they become cryptic Terraform errors:
./preflight.sh
-
Deploy:
terraform init terraform plan terraform apply
oci-alwaysfree-tf-stack/
├── main.tf # Provider, data sources, networking, compute
├── variables.tf # Input variables (2 required, 8 with defaults)
├── outputs.tf # IPs, OCIDs for downstream use
├── terraform.tfvars.example # Copy to terraform.tfvars and fill in
├── preflight.sh # Pre-deploy validation (OCI auth, keys, config)
└── .gitignore # Ignores tfstate, .terraform/, and tfvars
Only compartment_id and region are required. Everything else has sensible defaults.
| Variable | Type | Default | Description |
|---|---|---|---|
compartment_id |
string |
required | OCID of the compartment (usually your tenancy OCID) |
region |
string |
required | OCI region identifier (e.g., us-chicago-1) |
availability_domain_index |
number |
0 |
Index into the AD list. Change if your preferred AD is full. |
ssh_public_key |
string |
null |
SSH public key string. If null, reads from ssh_public_key_path. |
ssh_public_key_path |
string |
~/.ssh/id_ed25519.pub |
Path to SSH public key file (used when ssh_public_key is null) |
name_prefix |
string |
"oci-arm" |
Prefix for resource display names and DNS labels |
ocpus |
number |
4 |
ARM OCPUs (Always Free max: 4 total) |
memory_in_gbs |
number |
24 |
Memory in GB (Always Free max: 24 total) |
boot_volume_size_in_gbs |
number |
47 |
Boot volume in GB (Always Free max: 200 total) |
assign_public_ip |
bool |
true |
Assign a public IP (disable once you have alternative access, e.g., VPN) |
SSH in once the instance is running:
ssh -i ~/.ssh/your-key ubuntu@$(terraform output -raw instance_public_ip)The defaults stay within free-tier limits. If you change them, make sure the totals across all your instances stay under these caps:
| Resource | This stack uses | 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 |
| VCN | 1 | Included |
Cost Disclaimer: While this targets the Always Free tier, you are ultimately responsible for checking your OCI billing dashboard to ensure you haven't exceeded limits or selected the wrong region/shapes.
terraform destroyRemoves the instance, VCN, subnet, and gateway. Terraform state tracks what was created, so the destroy is clean and complete.
-
Intermittent 401-NotAuthenticated from Terraform: Check that your PEM key file (
~/.oci/oci_api_key.pem) ends cleanly with-----END PRIVATE KEY-----and nothing after it. The OCI CLI's Python SDK tolerates trailing junk, but the Terraform provider's Go SDK does not — it causes random auth failures where some API calls succeed and others don't. Thepreflight.shscript checks for this automatically. -
"Python X.Y not found" from OCI CLI: Install with
uv tool install oci-cliorpipx install oci-clito use an isolated Python environment instead of depending on the system Python.
-
OCI authentication: The provider uses
~/.oci/configby default. See the OCI Terraform provider docs for alternatives (env vars, instance principal, etc.). -
Public IP lifecycle: Once you have alternative access (e.g., VPN or Tailscale), set
assign_public_ip = falseand runterraform applyagain to drop the public IP. -
Finding your OCIDs:
# Compartment (tenancy OCID) oci iam compartment list --query 'data[0]."compartment-id"' --raw-output # Available regions oci iam region list --query 'data[*].name' --raw-output