π‘ Assuming you work on macOS
π‘ Multi-level subdomain (e.g. level2.level1.example.com) SSL may not working
Prerequisites Link to heading
- Oracle Cloud Account
- Cloudflare Account
- Custom Domain
Tech Stack Link to heading
- Oracle Cloud Infrastructure (OCI)
- Instance
- Network
- Cloudflare
- DNS
- SSL/TLS
- SSH
- Docker
- Docker Compose
- Nginx
- UFW
Step by step Link to heading
- Create a Compartment
- Create a Virtual Cloud Network
- Creates a VCN
- Adds an Internet Gateway which enables internet connections
- Creates and configures public and private subnets for the VCN
- Sets up route tables and security lists for the subnets
- Create a Virtual Machine Instance
- Choose the Name and Compartment
- Review the Placement
- Review the Security
- Review the Image and shape
- e.g. Ubuntu & VM.Standard.E2.1.Micro
- Review the Networking
- Review the Add SSH keys
- Generate SSH key pair and save to ~/.ssh/
- e.g. ssh-keygen -t ed25519 -b 4096 -C “[email protected]” -f ~/.ssh/my_ssh_key
- Upload the public key to OCI
- Generate SSH key pair and save to ~/.ssh/
- Connect to Your Instance
- chmod 400 <private_key_file>
- e.g. chmod 400 my_private_key
- ssh -i <private_key_file> @
- e.g. ssh -i ~/.ssh/my_private_key [email protected]
- chmod 400 <private_key_file>
- Add a Block Volume (Optional)
- Initial setup
sudo apt update && sudo apt upgrade -y sudo timedatectl set-timezone UTC - Install Docker suite
# Install Docker curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo \ "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io # Install Docker Compose v2 (optional) sudo apt install -y docker-compose-plugin # Add current USER to docker group sudo usermod -aG docker $USER # Enable docker to start on boot sudo systemctl enable --now docker # Check version docker version docker compose version - Create app directory
sudo mkdir welcome cd welcome - Create Docker Compose manifest
version: "3.9" services: welcome: image: docker/welcome-to-docker:latest container_name: welcome restart: always ports: - "127.0.0.1:8080:80" # bind to loopback only (nginx on host proxies to this) - Bring Compose up
docker compose up -d # Check status docker compose ps - Obtain Cloudflare Origin Certificate
- Save the files on the host
sudo mkdir -p /etc/ssl/cloudflare sudo tee /etc/ssl/cloudflare/origin.pem > /dev/null <<'EOF' ---PASTE THE ORIGIN CERTIFICATE (PEM) HERE--- EOF sudo tee /etc/ssl/cloudflare/origin.key > /dev/null <<'EOF' ---PASTE THE ORIGIN PRIVATE KEY (PEM) HERE--- EOF sudo chmod 600 /etc/ssl/cloudflare/origin.key sudo chmod 644 /etc/ssl/cloudflare/origin.pem - Install nginx
sudo apt install -y nginx - Create nginx config
upstream welcome_upstream { server 127.0.0.1:8080; } server { listen 80; server_name your.domain; # Redirect all HTTP to HTTPS return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name your.domain; ssl_certificate /etc/ssl/cloudflare/origin.pem; ssl_certificate_key /etc/ssl/cloudflare/origin.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; ssl_session_tickets off; # HSTS (careful with this in early testing; set long max-age in production) add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; # Security headers add_header X-Frame-Options SAMEORIGIN always; add_header X-Content-Type-Options nosniff always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header X-XSS-Protection "1; mode=block" always; add_header Content-Security-Policy "default-src 'self';" always; # Recommended limits client_max_body_size 10M; proxy_connect_timeout 10s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering off; location / { proxy_pass http://welcome_upstream; 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_http_version 1.1; proxy_set_header Connection ""; # preserve client IPs for logging } # Optional: a simple healthcheck endpoint proxied location = /health { proxy_pass http://welcome_upstream/; } access_log /var/log/nginx/welcome.access.log; error_log /var/log/nginx/welcome.error.log; } - Enable nginx
sudo ln -s /etc/nginx/sites-available/welcome /etc/nginx/sites-enabled/welcome sudo nginx -t sudo systemctl restart nginx sudo systemctl enable nginx - Firewall (UFW)
sudo apt install -y ufw sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status verbose - OCI Network Security
- The VCN/subnet security lists or Network Security Group allow ingress 80/443/22
- Cloudflare DNS + SSL settings
- In Cloudflare DNS
- create A record: app-name (e.g. welcome) β OCI_PUBLIC_IP (e.g. 152.12.30.45) β Proxied (orange cloud)
- In Cloudflare SSL/TLS
- Choose Full (strict)
- In Cloudflare DNS
- Verify
- From your browser visit: your FQDN (e.g. https://welcome.example.com) -> should show the Docker welcome page and the lock icon.