Complete Setup Example¶
This example demonstrates a complete Cloudflare infrastructure setup using all three modules together.
Scenario¶
We're setting up infrastructure for a SaaS company with:
- Marketing website hosted on Cloudflare Pages
- Documentation site hosted on Cloudflare Pages
- DNS records for various services
- Email routing for company email addresses
Directory Structure¶
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars (gitignored)
└── README.md
Configuration Files¶
main.tf¶
terraform {
required_version = ">= 1.0"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5.0"
}
}
}
provider "cloudflare" {
api_token = var.cloudflare_api_token
}
# ============================================================================
# Cloudflare Pages - Marketing Website
# ============================================================================
module "pages_marketing" {
source = "AutomationDojo/management/cloudflare//modules/pages"
version = "2.3.0"
account_id = var.cloudflare_account_id
projects = {
marketing = {
name = "company-marketing"
production_branch = "main"
github_owner = "company"
github_repo = "marketing-site"
build_command = "npm run build"
destination_dir = "dist"
custom_domain = "www.example.com"
deployment_configs = {
production = {
environment_variables = {
NODE_VERSION = "20"
NPM_VERSION = "10"
}
}
}
}
}
}
# ============================================================================
# Cloudflare Pages - Documentation
# ============================================================================
module "pages_docs" {
source = "AutomationDojo/management/cloudflare//modules/pages"
version = "2.3.0"
account_id = var.cloudflare_account_id
projects = {
documentation = {
name = "company-docs"
production_branch = "main"
github_owner = "company"
github_repo = "docs"
build_command = "mkdocs build"
destination_dir = "site"
custom_domain = "docs.example.com"
deployment_configs = {
production = {
environment_variables = {
PYTHON_VERSION = "3.11"
}
# For secrets use env_vars: { KEY = { value = var.secret, type = "secret" } }
}
}
}
}
}
# ============================================================================
# DNS Records
# ============================================================================
module "dns" {
source = "AutomationDojo/management/cloudflare//modules/dns"
version = "2.3.0"
zone_id = var.cloudflare_zone_id
records = [
# Root domain - redirect to www
{
name = "@"
type = "A"
value = "192.0.2.1"
ttl = 1
proxied = true
},
# API server
{
name = "api"
type = "A"
value = "192.0.2.10"
ttl = 1
proxied = true
},
# Database (not proxied - direct connection)
{
name = "db"
type = "A"
value = "192.0.2.20"
ttl = 3600
proxied = false
},
# Status page
{
name = "status"
type = "CNAME"
value = "status-page.provider.com"
ttl = 1
proxied = true
},
# Regional API endpoints
{
name = "us-api"
type = "A"
value = "192.0.2.30"
ttl = 300
proxied = true
},
{
name = "eu-api"
type = "A"
value = "192.0.2.31"
ttl = 300
proxied = true
},
# SPF record for email
{
name = "@"
type = "TXT"
value = "v=spf1 include:_spf.mx.cloudflare.net ~all"
ttl = 3600
proxied = false
},
# DKIM record
{
name = "cloudflare._domainkey"
type = "TXT"
value = "v=DKIM1; p=MIGfMA0GCSqGSIb3DQEBAQUAA..."
ttl = 3600
proxied = false
}
]
}
# ============================================================================
# Email Routing
# ============================================================================
module "email" {
source = "AutomationDojo/management/cloudflare//modules/email"
version = "2.3.0"
zone_id = var.cloudflare_zone_id
account_id = var.cloudflare_account_id
email_routing = {
enabled = true
# Destination addresses (need to be verified)
addresses = [
{
email = "[email protected]"
},
{
email = "[email protected]"
},
{
email = "[email protected]"
}
]
# Routing rules
rules = [
# Contact form
{
name = "Contact form submissions"
enabled = true
priority = 0
matchers = [
{
type = "literal"
field = "to"
value = "[email protected]"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
},
# Support emails
{
name = "Support requests"
enabled = true
priority = 1
matchers = [
{
type = "literal"
field = "to"
value = "[email protected]"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
},
# Sales emails
{
name = "Sales inquiries"
enabled = true
priority = 2
matchers = [
{
type = "literal"
field = "to"
value = "[email protected]"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
},
# Info emails
{
name = "General information"
enabled = true
priority = 3
matchers = [
{
type = "literal"
field = "to"
value = "[email protected]"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
},
# Catch-all (lowest priority)
{
name = "Catch all emails"
enabled = true
priority = 100
matchers = [
{
type = "wildcard"
field = "to"
value = "*@example.com"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
}
]
}
}
variables.tf¶
variable "cloudflare_api_token" {
description = "Cloudflare API token with required permissions"
type = string
sensitive = true
}
variable "cloudflare_account_id" {
description = "Cloudflare account ID"
type = string
}
variable "cloudflare_zone_id" {
description = "Cloudflare zone ID for example.com"
type = string
}
outputs.tf¶
# ============================================================================
# Pages Outputs
# ============================================================================
output "marketing_site_url" {
description = "Marketing website URL"
value = module.pages_marketing.custom_domains["marketing"]
}
output "marketing_site_default_url" {
description = "Marketing website pages.dev URL"
value = module.pages_marketing.default_urls["marketing"]
}
output "docs_site_url" {
description = "Documentation website URL"
value = module.pages_docs.custom_domains["documentation"]
}
output "docs_site_default_url" {
description = "Documentation pages.dev URL"
value = module.pages_docs.default_urls["documentation"]
}
# ============================================================================
# DNS Outputs
# ============================================================================
output "dns_records" {
description = "All DNS records created"
value = module.dns.records
sensitive = true
}
# ============================================================================
# Email Routing Outputs
# ============================================================================
output "email_routing_status" {
description = "Email routing configuration status"
value = {
enabled = module.email.email_routing.enabled
addresses_count = length(module.email.email_routing.addresses)
rules_count = length(module.email.email_routing.rules)
}
}
terraform.tfvars¶
# Add to .gitignore!
cloudflare_api_token = "your-api-token-here"
cloudflare_account_id = "your-account-id"
cloudflare_zone_id = "your-zone-id"
Deployment Steps¶
1. Initialize Terraform¶
2. Review the Plan¶
Expected output will show: - 2 Cloudflare Pages projects - 2 custom domain configurations - 8+ DNS records - Email routing configuration with 5 rules
3. Apply Configuration¶
Type yes when prompted.
4. Verify Email Addresses¶
After applying:
- Check email inboxes for verification emails from Cloudflare
- Click verification links in each email
- Wait for verification to complete
5. View Outputs¶
Example output:
marketing_site_url = "www.example.com"
marketing_site_default_url = "company-marketing.pages.dev"
docs_site_url = "docs.example.com"
docs_site_default_url = "company-docs.pages.dev"
email_routing_status = {
enabled = true
addresses_count = 3
rules_count = 5
}
Testing¶
Test Pages Deployments¶
- Push to your GitHub repositories
- Watch automatic deployments in Cloudflare dashboard
- Visit your custom domains
Test DNS Records¶
# Test A record
dig @1.1.1.1 api.example.com
# Test CNAME
dig @1.1.1.1 status.example.com
# Test TXT (SPF)
dig @1.1.1.1 example.com TXT
Test Email Routing¶
Send test emails to:
- [email protected]
- [email protected]
- [email protected]
- [email protected] (catch-all)
Verify they arrive at the correct destination addresses.
Maintenance¶
Update to Latest Version¶
# Update module version in main.tf
# Change ref=v2.0.1 to ref=v2.0.1
terraform init -upgrade
terraform plan
terraform apply
Add New DNS Record¶
# In main.tf, add to dns module's records list:
{
name = "staging"
type = "A"
value = "192.0.2.50"
ttl = 1
proxied = true
}
Then apply:
Add New Email Rule¶
# In main.tf, add to email module's rules list:
{
name = "Billing emails"
enabled = true
priority = 4
matchers = [
{
type = "literal"
field = "to"
value = "[email protected]"
}
]
actions = [
{
type = "forward"
value = ["[email protected]"]
}
]
}
Best Practices Demonstrated¶
- Module Versioning: Using specific version tags (
ref=v2.0.1) - Separation of Concerns: Each module handles one area
- Environment Variables: Properly configured for Pages builds
- DNS Organization: Clear naming and purpose for each record
- Email Priority: Specific rules before catch-all
- Security: API token in separate file (gitignored)
- Documentation: Clear outputs for easy reference
Cost Considerations¶
This setup uses Cloudflare's free tier:
- Pages: 500 builds/month (free)
- DNS: Unlimited records (free)
- Email Routing: 200 destination addresses (free)
- SSL/TLS: Automatic (free)
- DDoS Protection: Included (free)
Next Steps¶
- Set up Terraform Cloud for remote state
- Add monitoring and alerts
- Implement GitOps workflow with CI/CD
- Add more environments (staging, development)
- Configure Cloudflare Access for internal tools