A complete Infrastructure-as-Code solution for deploying a production-ready Docker Swarm cluster on Oracle Cloud Infrastructure's Always Free tier, featuring automatic provisioning, secure networking, and integrated monitoring with Traefik and Portainer.
- Fully Automated Deployment: One-command infrastructure provisioning and Swarm initialization
- OCI Free Tier Optimized: Maximizes the Always Free ARM A1.Flex instance (4 OCPUs, 24GB RAM)
- Production-Ready Stack: Docker Swarm with Traefik reverse proxy and Portainer management UI
- Secure by Default: Pre-configured security lists, private networking, and SSL/TLS support
- Infrastructure as Code: Complete Terraform configuration with state management
- Automatic Environment Configuration: Dynamic
.envfile generation with instance details
- Terraform >= 1.5.0 (Installation Guide)
- OCI CLI configured (Setup Instructions)
- SSH Client with key pair generation capability
- jq for JSON processing:
sudo apt-get install jq(optional but recommended)
- Oracle Cloud Infrastructure account with Free Tier access
- Administrative access to create resources in a compartment
- API keys configured for Terraform authentication
┌─────────────────────────────────────────────────────────────────┐
│ Oracle Cloud Infrastructure │
│ (Home Region) │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Virtual Cloud Network (VCN) │ │
│ │ 10.0.0.0/16 │ │
│ ├─────────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌────────────────────────────────────────────────┐ │ │
│ │ │ Public Subnet (10.0.0.0/24) │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────────────────────────────────┐ │ │ │
│ │ │ │ ARM A1.Flex Instance │ │ │ │
│ │ │ │ (Ubuntu 24.04 LTS) │ │ │ │
│ │ │ │ 4 OCPUs | 24GB RAM | 200GB │ │ │ │
│ │ │ │ │ │ │ │
│ │ │ │ ┌─────────────────────────────┐ │ │ │ │
│ │ │ │ │ Docker Swarm Manager │ │ │ │ │
│ │ │ │ ├─────────────────────────────┤ │ │ │ │
│ │ │ │ │ Traefik │ Portainer │ │ │ │ │
│ │ │ │ │ (Reverse │ (Management │ │ │ │ │
│ │ │ │ │ Proxy) │ UI) │ │ │ │ │
│ │ │ │ └─────────────────────────────┘ │ │ │ │
│ │ │ └──────────────────────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Security List Rules: │ │
│ │ • Ingress: SSH (22), HTTP (80), HTTPS (443) │ │
│ │ • Egress: All traffic allowed │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ Network Components: │
│ • Internet Gateway (for public access) │
│ • NAT Gateway (for outbound connections) │
│ • Service Gateway (for OCI services access) │
└─────────────────────────────────────────────────────────────────┘
oci-free-tier-provision/
├── LICENSE # GNU GPLv3 license
├── README.md # This file
├── infra/ # Network infrastructure configuration
│ ├── deploy.sh # Automated deployment script
│ ├── main.tf # VCN, subnet, and security configurations
│ ├── outputs.tf # Infrastructure outputs (subnet ID, etc.)
│ ├── terraform.tfstate # Terraform state file (auto-generated)
│ ├── terraform.tfvars # Variable values (create from template)
│ └── variables.tf # Variable definitions
├── vm/ # Compute instance configuration
│ ├── deploy.sh # VM deployment and env update script
│ ├── main.tf # ARM A1.Flex instance configuration
│ ├── outputs.tf # Instance outputs (IPs, etc.)
│ ├── terraform.tfstate # Terraform state file (auto-generated)
│ ├── terraform.tfvars # Variable values (create from template)
│ └── variables.tf # Variable definitions
└── swarm-deploy/ # Docker Swarm deployment scripts
├── deploySwarm.sh # Swarm initialization script
├── installDocker.sh # Docker installation script
├── portainer.yaml # Portainer stack configuration
└── traefik.yaml # Traefik stack configuration-
Clone the Repository
git clone https://github.com/yourusername/oci-free-tier-provision.git cd oci-free-tier-provision -
Generate SSH Keys for VM Access
# Generate a new SSH key pair for VM access ssh-keygen -t rsa -b 4096 -f ~/.ssh/oci_vm_key -C "your-email@example.com" # Set appropriate permissions chmod 600 ~/.ssh/oci_vm_key chmod 644 ~/.ssh/oci_vm_key.pub
-
Create API Keys (if not already done)
# Generate OCI API key pair mkdir -p ~/.oci openssl genrsa -out ~/.oci/oci_api_key.pem 2048 openssl rsa -pubout -in ~/.oci/oci_api_key.pem -out ~/.oci/oci_api_key_public.pem # Generate fingerprint openssl rsa -pubout -outform DER -in ~/.oci/oci_api_key.pem | openssl md5 -c
-
Upload Public Key to OCI Console
- Log in to OCI Console
- Navigate to Profile → User Settings → API Keys
- Click "Add API Key" and paste the contents of
~/.oci/oci_api_key_public.pem - Note the fingerprint displayed
-
Gather Required OCIDs
- Tenancy OCID: Profile → Tenancy → Copy OCID
- User OCID: Profile → User Settings → Copy OCID
- Compartment OCID: Identity → Compartments → Select compartment → Copy OCID
-
Create Environment File (
.envin project root)cat > .env << 'EOF' # OCI Authentication export TF_VAR_tenancy_ocid="ocid1.tenancy.oc1..aaaaaaaaxxxxxxxxxxxxxxxx" export TF_VAR_user_ocid="ocid1.user.oc1..aaaaaaaaxxxxxxxxxxxxxxxx" export TF_VAR_fingerprint="12:34:56:78:90:ab:cd:ef:12:34:56:78:90:ab:cd:ef" export TF_VAR_private_key_path="~/.oci/oci_api_key.pem" export TF_VAR_compartment_id="ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxx" export TF_VAR_region="sa-saopaulo-1" # Change to your home region # SSH Keys export TF_VAR_ssh_public_key="~/.oci/oci_api_key.pem" # For OCI API auth export TF_VAR_ssh_public_key2="$(cat ~/.ssh/oci_vm_key.pub)" # For VM SSH access EOF # Set secure permissions chmod 600 .env
-
Create Terraform Variable Files
infra/terraform.tfvars:
compartment_id = "ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxx" region = "sa-saopaulo-1" # Your home region ssh_public_key = "~/.oci/oci_api_key.pem" ssh_public_key2 = "ssh-rsa AAAAB3NzaC... your@email.com" # Content of oci_vm_key.pub
vm/terraform.tfvars:
# Same content as infra/terraform.tfvars compartment_id = "ocid1.compartment.oc1..aaaaaaaaxxxxxxxxxxxxxxxx" region = "sa-saopaulo-1" ssh_public_key = "~/.oci/oci_api_key.pem" ssh_public_key2 = "ssh-rsa AAAAB3NzaC... your@email.com"
-
Deploy Network Infrastructure
cd infra/ # Option 1: Manual deployment source ../.env terraform init terraform plan # Review the changes terraform apply # Option 2: Automated deployment (updates .env automatically) bash deploy.sh
-
Deploy Virtual Machine
cd ../vm/ # Option 1: Manual deployment source ../.env terraform init terraform plan # Review the changes terraform apply # Option 2: Automated deployment (updates .env and deploySwarm.sh) bash deploy.sh
-
Verify Deployment
# Source updated environment source ../.env # Test SSH connection ssh -i ~/.ssh/oci_vm_key ubuntu@$TF_VAR_instance_public_ip
-
Install Docker on the VM
cd ../swarm-deploy/ # Install Docker (run from your local machine) cat installDocker.sh | ssh -i ~/.ssh/oci_vm_key ubuntu@$TF_VAR_instance_public_ip "bash -s" # Note: You may need to reconnect SSH for group changes to take effect
-
Configure Portainer Domain (Optional)
Edit
swarm-deploy/portainer.yamlline 117:- "traefik.http.routers.portainer.rule=Host(`portainer.yourdomain.com`)" -
Deploy Docker Swarm Stack
# Deploy Swarm, Traefik, and Portainer cat deploySwarm.sh | ssh -i ~/.ssh/oci_vm_key ubuntu@$TF_VAR_instance_public_ip "bash -s"
-
Verify Swarm Status
ssh -i ~/.ssh/oci_vm_key ubuntu@$TF_VAR_instance_public_ip # Inside the VM: docker node ls docker service ls docker stack ls
-
Access Portainer (if domain configured)
- Navigate to:
https://portainer.yourdomain.com - Create admin account on first access
- Navigate to:
-
Configure DNS (for custom domains)
- Add A record pointing to
$TF_VAR_instance_public_ip - For Cloudflare users: Enable proxy for SSL termination
- Add A record pointing to
Edit vm/main.tf to adjust resources:
shape_config {
ocpus = 4 # 1-4 for free tier
memory_in_gbs = 24 # 1-24 for free tier
}
source_details {
boot_volume_size_in_gbs = 200 # Up to 200GB free
}Modify infra/main.tf for different network settings:
vcn_cidrs = ["10.0.0.0/16"] # VCN CIDR block
cidr_block = "10.0.0.0/24" # Subnet CIDR blockAdd custom security rules in infra/main.tf:
ingress_security_rules {
protocol = "6" # TCP
source = "0.0.0.0/0"
description = "Custom App"
tcp_options {
max = 8080
min = 8080
}
}Uncomment the certificate resolver lines in swarm-deploy/traefik.yaml:
- "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
- "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.letsencryptresolver.acme.email=your_email@here.com"
- "--certificatesresolvers.letsencryptresolver.acme.storage=/etc/traefik/letsencrypt/acme.json"To completely remove all resources:
# 1. Destroy VM first
cd vm/
terraform destroy -auto-approve
# 2. Destroy network infrastructure
cd ../infra/
terraform destroy -auto-approve
# 3. Clean up local files (optional)
rm -f terraform.tfstate* .terraform.lock.hcl
rm -rf .terraform/-
"Out of capacity" error for A1.Flex instances
- Try different availability domains
- Deploy during off-peak hours
- Consider using a different region
-
SSH connection refused
- Verify security list allows port 22
- Check if correct SSH key is being used
- Ensure instance is fully booted (wait 2-3 minutes)
-
Terraform state issues
- Run
terraform refreshto sync state - Check
.envfile is sourced:source .env - Verify all OCIDs are correct
- Run
-
Docker Swarm initialization fails
- Ensure Docker is installed:
docker --version - Check if user is in docker group:
groups - Verify advertise address matches instance IP
- Ensure Docker is installed:
# Check OCI CLI configuration
oci iam region list --output table
# Validate Terraform configuration
terraform validate
# Show Terraform execution plan
terraform plan
# View instance details
terraform show -json | jq '.values.root_module.resources[] | select(.type=="oci_core_instance")'
# Monitor instance boot
ssh -i ~/.ssh/oci_vm_key ubuntu@$TF_VAR_instance_public_ip "sudo tail -f /var/log/cloud-init-output.log"- OCI Free Tier Documentation
- Terraform OCI Provider Docs
- Docker Swarm Mode Overview
- Traefik Documentation
- Portainer Documentation
Contributions are welcome! Please feel free to submit a Pull Request.
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
- Free Tier Limitations: Idle compute instances may be reclaimed after 7 days if CPU utilization is below 20%
- Region Lock: Always Free resources are only available in your home region
- No SLA: Oracle provides no service level agreements for Always Free resources
- Email Restrictions: Port 25 (SMTP) is blocked by default on OCI instances
Created with ❤️ for the DevOps community. Happy deploying!