Created
May 25, 2025 22:47
-
-
Save benrowe/f61149b91e062d86f370f84ec1dc0067 to your computer and use it in GitHub Desktop.
Cloudflare tunnel & traefik setup via terraform
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# main.tf | |
# Configure the Cloudflare provider | |
# Make sure to set CLOUDFLARE_API_TOKEN environment variable or configure it directly here. | |
# For security, using environment variables is recommended. | |
# export CLOUDFLARE_API_TOKEN="YOUR_CLOUDFLARE_API_TOKEN" | |
# export CLOUDFLARE_ACCOUNT_ID="YOUR_CLOUDFLARE_ACCOUNT_ID" | |
terraform { | |
required_providers { | |
cloudflare = { | |
source = "cloudflare/cloudflare" | |
version = "~> 4.0" | |
} | |
local = { | |
source = "hashicorp/local" | |
version = "~> 2.0" | |
} | |
} | |
} | |
# --- Variables --- | |
variable "cloudflare_account_id" { | |
description = "Your Cloudflare Account ID." | |
type = string | |
} | |
variable "cloudflare_zone_id" { | |
description = "The Zone ID of your domain in Cloudflare." | |
type = string | |
} | |
variable "domain_name" { | |
description = "Your primary domain name (e.g., example.com)." | |
type = string | |
} | |
variable "traefik_subdomain" { | |
description = "The subdomain for your Traefik instance (e.g., traefik)." | |
type = string | |
default = "traefik" | |
} | |
variable "tunnel_name" { | |
description = "A unique name for your Cloudflare Zero Trust tunnel." | |
type = string | |
default = "traefik-tunnel" | |
} | |
# --- Cloudflare Tunnel Setup --- | |
resource "cloudflare_tunnel" "main_tunnel" { | |
account_id = var.cloudflare_account_id | |
name = var.tunnel_name | |
# The tunnel secret is automatically generated by Cloudflare. | |
# It will be used by the cloudflared Docker container. | |
} | |
# Create a CNAME DNS record for the Traefik subdomain, pointing to the tunnel. | |
# This makes your Traefik instance accessible via the tunnel. | |
resource "cloudflare_record" "traefik_cname" { | |
zone_id = var.cloudflare_zone_id | |
name = var.traefik_subdomain | |
value = "${cloudflare_tunnel.main_tunnel.id}.cfargotunnel.com" | |
type = "CNAME" | |
proxied = true # Ensure it's proxied through Cloudflare for Zero Trust features | |
} | |
# Define a tunnel route for the Traefik service. | |
# This tells the tunnel where to send traffic for the specified hostname. | |
resource "cloudflare_tunnel_route" "traefik_route" { | |
account_id = var.cloudflare_account_id | |
tunnel_id = cloudflare_tunnel.main_tunnel.id | |
hostname = "${var.traefik_subdomain}.${var.domain_name}" | |
# The service URL points to the internal Traefik instance within the Docker network. | |
# Traefik will be listening on port 80 (HTTP) or 443 (HTTPS) internally. | |
# We'll use HTTP for simplicity here, Traefik will handle HTTPS termination. | |
service = "http://traefik:80" | |
} | |
# --- Docker Compose File Generation --- | |
# Generate the docker-compose.yaml file content. | |
# This file will define the cloudflared and traefik services. | |
resource "local_file" "docker_compose_file" { | |
content = <<-EOT | |
version: '3.8' | |
services: | |
# Cloudflare Tunnel service | |
cloudflared: | |
image: cloudflare/cloudflared:latest | |
container_name: cloudflared | |
restart: unless-stopped | |
environment: | |
# The TUNNEL_TOKEN is securely retrieved from the Cloudflare tunnel resource. | |
TUNNEL_TOKEN: "${cloudflare_tunnel.main_tunnel.tunnel_token}" | |
command: tunnel run --token \${TUNNEL_TOKEN} | |
# Mount a volume for cloudflared's configuration and logs (optional but good practice) | |
volumes: | |
- ./cloudflared:/etc/cloudflared | |
networks: | |
- traefik_proxy | |
# Traefik Reverse Proxy service | |
traefik: | |
image: traefik:latest | |
container_name: traefik | |
restart: unless-stopped | |
security_opt: | |
- no-new-privileges:true | |
command: | |
# Enable Docker provider for Traefik | |
- --providers.docker=true | |
- --providers.docker.exposedbydefault=false | |
# Entrypoints for HTTP and HTTPS | |
- --entrypoints.web.address=:80 | |
- --entrypoints.websecure.address=:443 | |
# Enable Traefik Dashboard (optional, accessible via a separate route or locally) | |
- --api.dashboard=true | |
# Enable access logs (optional) | |
- --accesslog=true | |
# Enable Traefik logs (optional) | |
- --log.level=INFO | |
# Configure certificates (optional, for internal Traefik <-> service communication) | |
# If you want Traefik to handle SSL for your services, you'd add cert resolvers here. | |
# For Cloudflare tunnel, Cloudflare handles the public SSL, Traefik handles internal. | |
ports: | |
# The dashboard port (optional, for local access to Traefik dashboard) | |
- "8080:8080" | |
# Expose ports for Traefik to listen on for incoming HTTP/HTTPS traffic from the tunnel | |
- "80:80" | |
- "443:443" | |
volumes: | |
# Mount the Docker socket to allow Traefik to discover services | |
- /var/run/docker.sock:/var/run/docker.sock:ro | |
# Mount a volume for Traefik's dynamic configuration and logs | |
- ./traefik-data:/etc/traefik | |
networks: | |
- traefik_proxy | |
labels: | |
# Traefik labels to expose the dashboard internally (optional) | |
- "traefik.enable=true" | |
- "traefik.http.routers.traefik-dashboard.rule=Host(\`${var.traefik_subdomain}.${var.domain_name}\`) && PathPrefix(\`/dashboard\`)" | |
- "traefik.http.routers.traefik-dashboard.service=api@internal" | |
- "traefik.http.routers.traefik-dashboard.entrypoints=websecure" | |
# Redirect HTTP to HTTPS for the dashboard (optional) | |
- "traefik.http.routers.traefik-dashboard.middlewares=traefik-redirect-web-to-websecure" | |
# Middleware for HTTP to HTTPS redirection (if you want Traefik to handle it) | |
- "traefik.http.middlewares.traefik-redirect-web-to-websecure.redirectscheme.scheme=https" | |
- "traefik.http.middlewares.traefik-redirect-web-to-websecure.redirectscheme.permanent=true" | |
networks: | |
traefik_proxy: | |
external: true # Or create it if it doesn't exist. | |
# If you want Terraform to create the network, change to: | |
# name: traefik_proxy | |
# And remove the `external: true` line. | |
EOT | |
filename = "docker-compose.yaml" | |
} | |
# --- Outputs --- | |
output "tunnel_id" { | |
description = "The ID of the created Cloudflare Zero Trust tunnel." | |
value = cloudflare_tunnel.main_tunnel.id | |
} | |
output "tunnel_name" { | |
description = "The name of the created Cloudflare Zero Trust tunnel." | |
value = cloudflare_tunnel.main_tunnel.name | |
} | |
output "traefik_url" { | |
description = "The URL where your Traefik instance will be accessible via the tunnel." | |
value = "https://${var.traefik_subdomain}.${var.domain_name}" | |
} | |
output "docker_compose_file_path" { | |
description = "The path to the generated docker-compose.yaml file." | |
value = local_file.docker_compose_file.filename | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment