Author: Ori Levi & Claude
Last Updated: January 2026
Estimated Setup Time: 1-2 hours
- Overview
- Prerequisites
- Part 1: Oracle Cloud Free Tier Setup
- Part 2: Cloudflare R2 Setup
- Part 3: Vercel Setup & Domain Purchase
- Part 4: Connect Everything Together
- Architecture Diagram
- Cost Summary
- Troubleshooting
This guide walks you through setting up a complete microservice infrastructure using free tiers of major cloud providers:
- Oracle Cloud: Backend compute (ARM VM with 4 CPUs, 24GB RAM) + Database
- Cloudflare R2: Object storage (S3-compatible)
- Vercel: Frontend hosting + Domain management
All configuration is done via CLI tools for automation and reproducibility.
# Install Oracle Cloud CLI (OCI)
# macOS
brew install oci-cli
# Linux/Windows
curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh | bash
# Install Cloudflare Wrangler CLI
npm install -g wrangler
# Install Vercel CLI
npm install -g vercel
# Install jq for JSON parsing (optional but recommended)
brew install jq # macOS
# or: sudo apt install jq # Ubuntu/Debian
# Install Terraform (optional, for infrastructure as code)
brew install terraformssh-keygen -t rsa -b 4096 -C "your@email.com"
# Keys will be saved to ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub- Go to cloud.oracle.com/free
- Create an account with your email
- Verify your identity (credit card required but won't be charged)
- Wait for account activation (usually instant, sometimes up to 24 hours)
Free Tier Includes:
- 2 AMD Compute VMs (1/8 OCPU, 1GB RAM each)
- 4 ARM Ampere A1 cores + 24GB RAM (always free) β Best value!
- 2 Autonomous Databases (20GB each)
- 200GB Block Storage
- 10TB/month outbound data transfer
# Initial setup - creates config at ~/.oci/config
oci setup configYou'll be prompted for:
- User OCID: Find in OCI Console β Profile β User Settings
- Tenancy OCID: Find in OCI Console β Profile β Tenancy
- Region: e.g.,
us-ashburn-1,eu-frankfurt-1,me-jeddah-1 - Generate new API key pair?: Yes
After generation, upload the public key:
- Go to OCI Console β Profile β API Keys
- Click "Add API Key"
- Choose "Paste Public Key"
- Paste content of
~/.oci/oci_api_key_public.pem
#!/bin/bash
# create-network.sh
# Set your compartment ID (find in OCI Console β Identity β Compartments)
COMPARTMENT_ID="ocid1.compartment.oc1..your_compartment_id"
# Create VCN (Virtual Cloud Network)
VCN_ID=$(oci network vcn create \
--compartment-id "$COMPARTMENT_ID" \
--cidr-block "10.0.0.0/16" \
--display-name "microservice-vcn" \
--dns-label "microservicevcn" \
--query 'data.id' --raw-output)
echo "VCN created: $VCN_ID"
# Create Internet Gateway
IGW_ID=$(oci network internet-gateway create \
--compartment-id "$COMPARTMENT_ID" \
--vcn-id "$VCN_ID" \
--is-enabled true \
--display-name "microservice-igw" \
--query 'data.id' --raw-output)
echo "Internet Gateway created: $IGW_ID"
# Create Route Table with internet access
RT_ID=$(oci network route-table create \
--compartment-id "$COMPARTMENT_ID" \
--vcn-id "$VCN_ID" \
--route-rules '[{"cidrBlock":"0.0.0.0/0","networkEntityId":"'$IGW_ID'"}]' \
--display-name "microservice-rt" \
--query 'data.id' --raw-output)
echo "Route Table created: $RT_ID"
# Create Security List (firewall rules)
SL_ID=$(oci network security-list create \
--compartment-id "$COMPARTMENT_ID" \
--vcn-id "$VCN_ID" \
--display-name "microservice-sl" \
--ingress-security-rules '[
{"protocol":"6","source":"0.0.0.0/0","tcpOptions":{"destinationPortRange":{"min":22,"max":22}}},
{"protocol":"6","source":"0.0.0.0/0","tcpOptions":{"destinationPortRange":{"min":80,"max":80}}},
{"protocol":"6","source":"0.0.0.0/0","tcpOptions":{"destinationPortRange":{"min":443,"max":443}}},
{"protocol":"6","source":"0.0.0.0/0","tcpOptions":{"destinationPortRange":{"min":3000,"max":3000}}}
]' \
--egress-security-rules '[{"protocol":"all","destination":"0.0.0.0/0"}]' \
--query 'data.id' --raw-output)
echo "Security List created: $SL_ID"
# Create Subnet
SUBNET_ID=$(oci network subnet create \
--compartment-id "$COMPARTMENT_ID" \
--vcn-id "$VCN_ID" \
--cidr-block "10.0.1.0/24" \
--display-name "microservice-subnet" \
--dns-label "microservicesub" \
--route-table-id "$RT_ID" \
--security-list-ids '["'$SL_ID'"]' \
--query 'data.id' --raw-output)
echo "Subnet created: $SUBNET_ID"
# Save IDs for later use
cat > network-ids.env << EOF
VCN_ID=$VCN_ID
IGW_ID=$IGW_ID
RT_ID=$RT_ID
SL_ID=$SL_ID
SUBNET_ID=$SUBNET_ID
EOF
echo "Network IDs saved to network-ids.env"#!/bin/bash
# create-instance.sh
source network-ids.env
COMPARTMENT_ID="ocid1.compartment.oc1..your_compartment_id"
# Get Availability Domain
AVAILABILITY_DOMAIN=$(oci iam availability-domain list \
--compartment-id "$COMPARTMENT_ID" \
--query 'data[0].name' --raw-output)
echo "Using Availability Domain: $AVAILABILITY_DOMAIN"
# Get latest Ubuntu 22.04 ARM image
IMAGE_ID=$(oci compute image list \
--compartment-id "$COMPARTMENT_ID" \
--operating-system "Canonical Ubuntu" \
--operating-system-version "22.04" \
--shape "VM.Standard.A1.Flex" \
--sort-by TIMECREATED \
--sort-order DESC \
--query 'data[0].id' --raw-output)
echo "Using Image: $IMAGE_ID"
# Create ARM instance (4 OCPUs, 24GB RAM - free tier max)
INSTANCE_ID=$(oci compute instance launch \
--compartment-id "$COMPARTMENT_ID" \
--availability-domain "$AVAILABILITY_DOMAIN" \
--shape "VM.Standard.A1.Flex" \
--shape-config '{"ocpus": 4, "memoryInGBs": 24}' \
--subnet-id "$SUBNET_ID" \
--image-id "$IMAGE_ID" \
--display-name "microservice-server" \
--assign-public-ip true \
--ssh-authorized-keys-file ~/.ssh/id_rsa.pub \
--wait-for-state RUNNING \
--query 'data.id' --raw-output)
echo "Instance created: $INSTANCE_ID"
# Get Public IP
PUBLIC_IP=$(oci compute instance list-vnics \
--instance-id "$INSTANCE_ID" \
--query 'data[0]."public-ip"' --raw-output)
echo "============================================"
echo "β
Instance ready!"
echo "Public IP: $PUBLIC_IP"
echo "SSH Command: ssh -i ~/.ssh/id_rsa ubuntu@$PUBLIC_IP"
echo "============================================"
# Save for later
echo "INSTANCE_ID=$INSTANCE_ID" >> network-ids.env
echo "PUBLIC_IP=$PUBLIC_IP" >> network-ids.env
β οΈ Note: ARM instances are in high demand. If creation fails with "Out of capacity", try:
- Different availability domain
- Smaller configuration (2 OCPUs, 12GB RAM)
- Wait and retry later
#!/bin/bash
# create-database.sh
COMPARTMENT_ID="ocid1.compartment.oc1..your_compartment_id"
DB_NAME="microservicedb"
ADMIN_PASSWORD="YourSecurePassword123!" # Must have: uppercase, lowercase, number, 12+ chars
# Create Autonomous Database (Always Free)
DB_ID=$(oci db autonomous-database create \
--compartment-id "$COMPARTMENT_ID" \
--db-name "$DB_NAME" \
--display-name "$DB_NAME" \
--admin-password "$ADMIN_PASSWORD" \
--cpu-core-count 1 \
--data-storage-size-in-tbs 1 \
--db-workload "OLTP" \
--is-free-tier true \
--wait-for-state AVAILABLE \
--query 'data.id' --raw-output)
echo "Database created: $DB_ID"
# Download connection wallet
oci db autonomous-database generate-wallet \
--autonomous-database-id "$DB_ID" \
--password "$ADMIN_PASSWORD" \
--file wallet.zip
echo "Connection wallet saved to wallet.zip"
# Get connection strings
oci db autonomous-database get \
--autonomous-database-id "$DB_ID" \
--query 'data."connection-strings"'After SSHing into your instance, run this setup script:
#!/bin/bash
# server-setup.sh - Run this ON the Oracle instance
set -e
echo "π Setting up server..."
# Update system
sudo apt update && sudo apt upgrade -y
# Install essential packages
sudo apt install -y \
docker.io \
docker-compose \
git \
curl \
wget \
unzip \
nginx \
certbot \
python3-certbot-nginx \
build-essential
# Enable Docker
sudo systemctl enable docker
sudo systemctl start docker
sudo usermod -aG docker $USER
# Install Node.js 20 (LTS)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs
# Verify installations
echo "Node.js version: $(node --version)"
echo "npm version: $(npm --version)"
echo "Docker version: $(docker --version)"
# Install OpenCode (AI coding assistant)
sudo npm install -g @anthropic-ai/opencode
# Alternative: Install OpenCode Go version
# curl -fsSL https://get.opencode.ai | bash
# Install PM2 for process management
sudo npm install -g pm2
# Configure firewall (iptables)
sudo iptables -I INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 443 -j ACCEPT
sudo iptables -I INPUT -p tcp --dport 3000 -j ACCEPT
# Make iptables rules persistent
sudo apt install -y iptables-persistent
sudo netfilter-persistent save
# Create application directory
sudo mkdir -p /opt/microservice
sudo chown $USER:$USER /opt/microservice
echo "β
Server setup complete!"
echo ""
echo "Next steps:"
echo "1. Configure Nginx: sudo nano /etc/nginx/sites-available/default"
echo "2. Set up SSL: sudo certbot --nginx -d your-domain.com"
echo "3. Deploy your application to /opt/microservice"# Create Nginx configuration
sudo tee /etc/nginx/sites-available/microservice << 'EOF'
server {
listen 80;
server_name api.your-domain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}
EOF
# Enable the site
sudo ln -sf /etc/nginx/sites-available/microservice /etc/nginx/sites-enabled/
# Test and reload
sudo nginx -t && sudo systemctl reload nginx
# Get SSL certificate (after DNS is configured)
sudo certbot --nginx -d api.your-domain.com --non-interactive --agree-tos -m your@email.com- Go to dash.cloudflare.com/sign-up
- Create account and verify email
- Add your domain (or use Cloudflare's free subdomain for testing)
Free Tier Includes:
- 10GB storage
- 1 million Class A operations/month (writes)
- 10 million Class B operations/month (reads)
- No egress fees! π
# Login to Cloudflare (opens browser for OAuth)
wrangler login
# Verify login
wrangler whoamiFor headless/CI environments:
# Create API token at: dash.cloudflare.com/profile/api-tokens
# Required permissions: Account.Workers R2 Storage:Edit
export CLOUDFLARE_API_TOKEN="your-api-token"
export CLOUDFLARE_ACCOUNT_ID="your-account-id" # Find in dashboard URL or sidebar#!/bin/bash
# create-r2-bucket.sh
BUCKET_NAME="microservice-storage"
# Create bucket
wrangler r2 bucket create "$BUCKET_NAME"
# List buckets to verify
wrangler r2 bucket list
# Get bucket info
wrangler r2 bucket info "$BUCKET_NAME"For S3-compatible API access (needed for your microservice):
- Go to Cloudflare Dashboard β R2 β Overview
- Click "Manage R2 API Tokens"
- Click "Create API Token"
- Set permissions: "Object Read & Write"
- Specify bucket (or all buckets)
- Copy the Access Key ID and Secret Access Key
Or via API:
# Create API token programmatically
curl -X POST "https://api.cloudflare.com/client/v4/accounts/$CLOUDFLARE_ACCOUNT_ID/r2/buckets/$BUCKET_NAME/tokens" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"name": "Microservice R2 Token",
"permissions": ["object-read-write"]
}'// Example: Node.js with AWS SDK v3
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
const r2Client = new S3Client({
region: "auto",
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
// Upload file
await r2Client.send(new PutObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: "uploads/file.pdf",
Body: fileBuffer,
ContentType: "application/pdf",
}));
// Download file
const response = await r2Client.send(new GetObjectCommand({
Bucket: process.env.R2_BUCKET_NAME,
Key: "uploads/file.pdf",
}));# Login (opens browser)
vercel login
# Or login with email
vercel login your@email.com
# Or use token for CI/CD
export VERCEL_TOKEN="your-vercel-token"# Check domain availability
vercel domains inspect your-domain.com
# Buy domain (requires payment method on Vercel account)
vercel domains buy your-domain.com
# Or add existing domain
vercel domains add your-domain.com
# List your domains
vercel domains ls# Navigate to your project
cd /path/to/your/frontend
# Initialize Vercel project (first time)
vercel
# Deploy to production
vercel --prod
# Link custom domain to project
vercel domains add your-domain.com --project your-project-name# Add environment variables
vercel env add NEXT_PUBLIC_API_URL production
# Enter value: https://api.your-domain.com
vercel env add DATABASE_URL production
# Enter your database connection string
# List environment variables
vercel env lsIf your domain is on Cloudflare:
#!/bin/bash
# configure-dns.sh
DOMAIN="your-domain.com"
ORACLE_IP="your-oracle-instance-ip"
# Get Zone ID
ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$DOMAIN" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" | jq -r '.result[0].id')
echo "Zone ID: $ZONE_ID"
# Add A record for API subdomain
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "A",
"name": "api",
"content": "'$ORACLE_IP'",
"ttl": 1,
"proxied": true
}'
# Add CNAME for Vercel (if not using Vercel DNS)
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "@",
"content": "cname.vercel-dns.com",
"ttl": 1,
"proxied": false
}'
echo "DNS records configured!"Create a .env.production file:
# Oracle Cloud Database
DATABASE_URL=postgresql://admin:password@your-db-host:5432/microservicedb
# Or for Oracle Autonomous DB (with wallet)
DATABASE_URL=oracle://admin:password@tcps://adb.region.oraclecloud.com:1522/dbname_tp
# Cloudflare R2
R2_ACCOUNT_ID=your-cloudflare-account-id
R2_ACCESS_KEY_ID=your-r2-access-key
R2_SECRET_ACCESS_KEY=your-r2-secret-key
R2_BUCKET_NAME=microservice-storage
R2_ENDPOINT=https://${R2_ACCOUNT_ID}.r2.cloudflarestorage.com
# URLs
FRONTEND_URL=https://your-domain.com
API_URL=https://api.your-domain.com
# OpenCode (for AI assistance on server)
ANTHROPIC_API_KEY=your-anthropic-api-key#!/bin/bash
# full-setup.sh - Complete infrastructure setup
set -e
echo "============================================"
echo "π Full Infrastructure Setup"
echo "============================================"
# Configuration
DOMAIN="your-domain.com"
PROJECT_NAME="my-microservice"
COMPARTMENT_ID="ocid1.compartment.oc1..xxx"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
print_status() {
echo -e "${GREEN}β
$1${NC}"
}
print_warning() {
echo -e "${YELLOW}β οΈ $1${NC}"
}
print_error() {
echo -e "${RED}β $1${NC}"
}
# Check prerequisites
echo "Checking prerequisites..."
command -v oci >/dev/null 2>&1 || { print_error "OCI CLI not installed"; exit 1; }
command -v wrangler >/dev/null 2>&1 || { print_error "Wrangler not installed"; exit 1; }
command -v vercel >/dev/null 2>&1 || { print_error "Vercel CLI not installed"; exit 1; }
print_status "All CLI tools installed"
# Step 1: Oracle Cloud Setup
echo ""
echo "Step 1: Oracle Cloud Setup"
echo "βββββββββββββββββββββββββββββ"
# ... (Insert Oracle setup commands from sections 1.3-1.5)
print_status "Oracle Cloud infrastructure created"
# Step 2: Cloudflare R2 Setup
echo ""
echo "Step 2: Cloudflare R2 Setup"
echo "βββββββββββββββββββββββββββββ"
BUCKET_NAME="${PROJECT_NAME}-storage"
wrangler r2 bucket create "$BUCKET_NAME" 2>/dev/null || print_warning "Bucket may already exist"
print_status "R2 bucket ready: $BUCKET_NAME"
# Step 3: Vercel Setup
echo ""
echo "Step 3: Vercel Setup"
echo "βββββββββββββββββββββββββββββ"
# Deploy (assumes you're in project directory)
vercel --prod --yes 2>/dev/null || print_warning "Vercel deployment skipped"
print_status "Vercel deployment complete"
# Step 4: Generate summary
echo ""
echo "============================================"
echo "π Setup Complete!"
echo "============================================"
echo ""
echo "Resources:"
echo " β’ Oracle Instance: $PUBLIC_IP"
echo " β’ R2 Bucket: $BUCKET_NAME"
echo " β’ Frontend: https://$DOMAIN"
echo " β’ API: https://api.$DOMAIN"
echo ""
echo "Next Steps:"
echo " 1. SSH to server: ssh ubuntu@$PUBLIC_IP"
echo " 2. Run server setup script"
echo " 3. Configure DNS records"
echo " 4. Deploy your microservice code"
echo ""βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β USERS β
βββββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β
βΌ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Cloudflare DNS + CDN β
β (DDoS protection, caching, SSL termination) β
ββββββββββββββββββββ¬ββββββββββββββββββββββββββ¬βββββββββββββββββββββ
β β
βΌ βΌ
ββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ
β Vercel (Frontend) β β Oracle Cloud (Backend) β
β βββββββββββββββββββββββββββ β βββββββββββββββββββββββββββββββ
β β’ Next.js / React App β β β’ ARM VM (4 CPU, 24GB RAM) β
β β’ Static assets (CDN) β β β’ Docker containers β
β β’ Serverless functions β β β’ Nginx reverse proxy β
β β’ Auto SSL β β β’ OpenCode AI assistant β
β β’ your-domain.com β β β’ api.your-domain.com β
ββββββββββββββββββββββββββββββ βββββββββββββββββ¬βββββββββββββββββ
β
βββββββββββββββββββββββββββββββΌββββββββββββββββββ
β β β
βΌ βΌ βΌ
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ βββββββββββββββββ
β Cloudflare R2 β β Oracle Autonomous DB β β OpenCode β
β βββββββββββββββββββββββ β βββββββββββββββββββββββ β ββββββββββββββ
β β’ Object storage β β β’ PostgreSQL/Oracle β β β’ AI Pair β
β β’ S3-compatible API β β β’ 20GB free storage β β Programmingβ
β β’ No egress fees β β β’ Auto scaling β β β’ Code reviewβ
β β’ Global CDN β β β’ Auto backups β β β’ Debugging β
ββββββββββββββββββββββββββ ββββββββββββββββββββββββββ βββββββββββββββββ
| Service | Free Tier Includes | Paid Tier Starts |
|---|---|---|
| Oracle Cloud | 4 ARM cores, 24GB RAM, 200GB storage, 2 DBs (20GB each), 10TB/mo egress | $0 (always free tier) |
| Cloudflare R2 | 10GB storage, 1M writes, 10M reads/month, no egress fees | $0.015/GB storage |
| Vercel | 100GB bandwidth, 100GB-hours compute, automatic SSL | $20/mo (Pro) |
| Domain | N/A | ~$10-20/year |
Total Monthly Cost: $0-2 (just domain renewal)
Problem: "Out of capacity" error when creating ARM instance
# Solution 1: Try different availability domain
oci iam availability-domain list --compartment-id $COMPARTMENT_ID
# Solution 2: Reduce instance size
--shape-config '{"ocpus": 2, "memoryInGBs": 12}'
# Solution 3: Use automation to retry
while true; do
oci compute instance launch ... && break
sleep 60
doneProblem: Can't SSH to instance
# Check security list rules
oci network security-list get --security-list-id $SL_ID
# Verify public IP assigned
oci compute instance list-vnics --instance-id $INSTANCE_ID
# Check SSH key permissions
chmod 600 ~/.ssh/id_rsaProblem: Access denied when uploading
# Verify credentials
wrangler whoami
# Check bucket permissions
wrangler r2 bucket info $BUCKET_NAME
# Test with wrangler directly
echo "test" | wrangler r2 object put $BUCKET_NAME/test.txtProblem: Domain not resolving
# Check DNS propagation
dig your-domain.com
nslookup your-domain.com
# Verify Vercel domain config
vercel domains inspect your-domain.com# Oracle Cloud
oci compute instance list --compartment-id $COMPARTMENT_ID
oci compute instance get --instance-id $INSTANCE_ID
oci compute instance action --action STOP --instance-id $INSTANCE_ID
oci compute instance action --action START --instance-id $INSTANCE_ID
# Cloudflare R2
wrangler r2 bucket list
wrangler r2 object list $BUCKET_NAME
wrangler r2 object get $BUCKET_NAME/path/to/file
wrangler r2 object put $BUCKET_NAME/path/to/file --file ./local-file
# Vercel
vercel ls # List deployments
vercel logs # View deployment logs
vercel env ls # List environment variables
vercel domains ls # List domains
vercel --prod # Deploy to production- Oracle Cloud Free Tier Documentation
- Cloudflare R2 Documentation
- Vercel Documentation
- OpenCode Documentation
Happy deploying! π