cloudflare-tunnel-ec2-deployment

Deploy containerized applications to AWS EC2 and expose them publicly via Cloudflare Tunnel with automatic HTTPS. Eliminates need for load balancers, SSL certificates, or public inbound ports.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "cloudflare-tunnel-ec2-deployment" with this command: npx skills add stakpak/community-paks/stakpak-community-paks-cloudflare-tunnel-ec2-deployment

Deploying Applications on EC2 with Cloudflare Tunnel

Quick Start

Provision and Deploy

# Get VPC and subnet
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" --query 'Vpcs[0].VpcId' --output text)
SUBNET_ID=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" "Name=map-public-ip-on-launch,Values=true" --query 'Subnets[0].SubnetId' --output text)

# Create security group
SG_ID=$(aws ec2 create-security-group --group-name app-sg --description "Security group for app deployment" --vpc-id $VPC_ID --query 'GroupId' --output text)
aws ec2 authorize-security-group-ingress --group-id $SG_ID --protocol tcp --port 22 --cidr 0.0.0.0/0

# Launch instance
AMI_ID=$(aws ec2 describe-images --owners amazon --filters "Name=name,Values=al2023-ami-2023*-x86_64" "Name=state,Values=available" --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text)
INSTANCE_ID=$(aws ec2 run-instances --image-id $AMI_ID --instance-type t3.small --key-name app-deploy-key --security-group-ids $SG_ID --subnet-id $SUBNET_ID --associate-public-ip-address --query 'Instances[0].InstanceId' --output text)

Prerequisites

  • AWS CLI configured with appropriate permissions (EC2, VPC)
  • Cloudflare account with a domain configured
  • Docker-compatible application with Dockerfile
  • Cloudflare Tunnel token from Zero Trust dashboard

Infrastructure Setup

1. VPC Resources

# Get default VPC
VPC_ID=$(aws ec2 describe-vpcs --filters "Name=isDefault,Values=true" \
  --query 'Vpcs[0].VpcId' --output text)

# Get public subnet
SUBNET_ID=$(aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" "Name=map-public-ip-on-launch,Values=true" \
  --query 'Subnets[0].SubnetId' --output text)

2. Security Group

# Create security group - only SSH needed for management
# Cloudflare Tunnel handles all inbound traffic
SG_ID=$(aws ec2 create-security-group \
  --group-name app-sg \
  --description "Security group for app deployment" \
  --vpc-id $VPC_ID \
  --query 'GroupId' --output text)

# Allow SSH access (restrict to your IP in production)
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp --port 22 --cidr 0.0.0.0/0

Key insight: With Cloudflare Tunnel, you don't need to open ports 80/443. The tunnel creates outbound connections to Cloudflare, eliminating inbound attack surface.

3. SSH Key Pair

# Generate local key
ssh-keygen -t ed25519 -f ~/.ssh/app-deploy-key -N "" -C "app-deploy"

# Import to AWS
aws ec2 import-key-pair \
  --key-name app-deploy-key \
  --public-key-material fileb://~/.ssh/app-deploy-key.pub

Important: Never delete SSH keys after creation - you'll be locked out of the instance.

4. Launch EC2 Instance

# Get latest Amazon Linux 2023 AMI
AMI_ID=$(aws ec2 describe-images --owners amazon \
  --filters "Name=name,Values=al2023-ami-2023*-x86_64" "Name=state,Values=available" \
  --query 'sort_by(Images, &CreationDate)[-1].ImageId' --output text)

# Launch instance
INSTANCE_ID=$(aws ec2 run-instances \
  --image-id $AMI_ID \
  --instance-type t3.small \
  --key-name app-deploy-key \
  --security-group-ids $SG_ID \
  --subnet-id $SUBNET_ID \
  --associate-public-ip-address \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=my-app}]' \
  --query 'Instances[0].InstanceId' --output text)

# Wait for instance and get IP
aws ec2 wait instance-running --instance-ids $INSTANCE_ID
PUBLIC_IP=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID \
  --query 'Reservations[0].Instances[0].PublicIpAddress' --output text)

Instance Sizing

Instance TypeUse Case
t3.microSimple static sites, minimal APIs
t3.smallStandard web apps, Node.js/Python services
t3.mediumApps with build steps, multiple containers

Application Deployment

Install Docker

# Wait for SSH to be ready (30-60 seconds after instance running)
sleep 45

# Install Docker
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "sudo dnf install -y docker git && \
   sudo systemctl enable --now docker && \
   sudo usermod -aG docker ec2-user"

Deploy Application

# Clone and build application
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "git clone <REPO_URL> app && \
   cd app && \
   echo 'ENV_VAR=value' > .env"

# Build and run container
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "cd app && \
   sudo docker build -t myapp:latest . && \
   sudo docker run -d --name myapp --restart unless-stopped -p 80:80 myapp:latest"

Production Dockerfile Pattern

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
ARG VITE_API_KEY
ENV VITE_API_KEY=$VITE_API_KEY
RUN npm run build

FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Reasoning: Multi-stage builds reduce image size and don't include build tools in production.

Cloudflare Tunnel Configuration

Install cloudflared

ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "curl -fsSL https://pkg.cloudflare.com/cloudflared.repo | \
   sudo tee /etc/yum.repos.d/cloudflared.repo && \
   sudo yum install -y cloudflared"

Install Tunnel as Service

# Use the token from Cloudflare Zero Trust dashboard
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "sudo cloudflared service install <TUNNEL_TOKEN>"

Reasoning: Installing as a systemd service ensures the tunnel auto-starts on reboot and restarts on failure.

Verify Tunnel Connection

ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "sudo systemctl status cloudflared"

Look for: Registered tunnel connection messages (should see 4 connections for a healthy tunnel).

DNS Configuration

Cloudflare Dashboard Method

In Cloudflare Zero Trust dashboard:

  1. Navigate to Networks → Tunnels
  2. Select your tunnel → Public Hostname tab
  3. Add hostname:
    • Subdomain: app
    • Domain: example.com
    • Type: HTTP
    • URL: localhost:80

This automatically creates a CNAME record pointing to <tunnel-id>.cfargotunnel.com.

API Method

# Create DNS record via API
curl -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \
  -H "Authorization: Bearer $CF_API_TOKEN" \
  -H "Content-Type: application/json" \
  --data '{
    "type": "CNAME",
    "name": "app",
    "content": "<tunnel-id>.cfargotunnel.com",
    "proxied": true
  }'

Validation

Test Deployment

# Test via tunnel (may take 1-2 minutes for DNS propagation)
curl -sI https://app.example.com/

# Check container logs
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "sudo docker logs myapp"

# Verify tunnel health
ssh -i ~/.ssh/app-deploy-key ec2-user@$PUBLIC_IP \
  "sudo journalctl -u cloudflared -n 20"

Success Criteria

  • HTTP 200 response from public URL
  • No errors in container logs
  • 4 registered tunnel connections

Outputs Documentation

Create an outputs file for future reference:

{
  "app_url": "https://app.example.com",
  "ec2_instance_id": "<instance-id>",
  "ec2_public_ip": "<public-ip>",
  "aws_region": "us-east-1",
  "ssh_command": "ssh -i ~/.ssh/app-deploy-key ec2-user@<public-ip>",
  "cloudflare_tunnel_id": "<tunnel-id>",
  "container_name": "myapp"
}

Security Considerations

  • Restrict SSH access - Use specific IP ranges instead of 0.0.0.0/0
  • No public ports needed - Cloudflare Tunnel eliminates need for ports 80/443
  • Rotate tunnel tokens - If compromised, regenerate in Cloudflare dashboard
  • Use secrets management - Don't hardcode API keys in Dockerfiles; use build args or runtime env vars
  • Enable Cloudflare Access - Add authentication layer for sensitive applications

Troubleshooting

IssueDiagnosisSolution
DNS not resolvingdig @1.1.1.1 app.example.comWait for propagation or check CNAME record
Tunnel not connectingCheck systemctl status cloudflaredVerify token, check outbound connectivity on port 7844
Container not startingdocker logs myappCheck Dockerfile, environment variables
502 Bad GatewayTunnel running but app not respondingVerify container is listening on correct port

References

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

terrateam-usage-guide

No summary provided by upstream source.

Repository SourceNeeds Review
General

vllm-deployment

No summary provided by upstream source.

Repository SourceNeeds Review
General

coolify-deployment

No summary provided by upstream source.

Repository SourceNeeds Review
General

k3s-backup

No summary provided by upstream source.

Repository SourceNeeds Review