Created
November 16, 2023 03:34
-
-
Save jmdfm/dd8414cc43b39f3a9c70d0e6093f246a to your computer and use it in GitHub Desktop.
The Terraform config to setup a managed LB, Firewall, PSQL DB, Redis and droplets on DO, with bonus ECR on AWS.
This file contains 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
resource "aws_ecr_repository" "repo" { | |
name = "${var.product_name}-app" | |
} | |
resource "aws_ecr_lifecycle_policy" "ecr-lifecycle-policy" { | |
repository = aws_ecr_repository.repo.name | |
policy = jsonencode({ | |
rules = [ | |
{ | |
rulePriority = 1 | |
description = "Keep last 20 images" | |
selection = { | |
tagStatus = "any", | |
countType = "imageCountMoreThan", | |
countNumber = 20 | |
} | |
action = { | |
type = "expire" | |
} | |
} | |
] | |
}) | |
} |
This file contains 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
# This config assumes Cloudflare is in front of the LB doing TLS termination. | |
variable "do_token" { | |
sensitive = true | |
} | |
variable "ssh_key_names" { | |
sensitive = true | |
} | |
variable "digitalocean_region" { | |
description = "For example: nyc1, nyc2, ams2, ams3, fra2" | |
default = "sfo3" | |
} | |
# Name of your project. Will be prepended to most resources | |
variable "product_name" { | |
type = string | |
default = "your-app" | |
} | |
# The size we want our droplets to be. | |
# Can view slugs (valid options) https://slugs.do-api.dev/ | |
variable "droplet_size" { | |
type = string | |
default = "s-1vcpu-1gb" | |
} | |
# The size we want our database images to be to be. | |
variable "database_size" { | |
type = string | |
default = "db-s-1vcpu-1gb" | |
} | |
# The size we want our redis images to be to be. | |
variable "redis_size" { | |
type = string | |
default = "db-s-1vcpu-1gb" | |
} | |
data "digitalocean_ssh_keys" "keys" { | |
filter { | |
key = "name" | |
values = var.ssh_key_names | |
} | |
} | |
# Create a VPC | |
resource "digitalocean_vpc" "vpc" { | |
name = "${var.product_name}-vpc" | |
region = var.digitalocean_region | |
ip_range = "192.168.0.0/16" | |
} | |
resource "digitalocean_droplet" "web" { | |
count = 2 | |
image = "docker-20-04" | |
name = "${var.product_name}-${var.digitalocean_region}-webworker-${count.index + 1}" | |
region = var.digitalocean_region | |
size = var.droplet_size | |
tags = ["${var.product_name}-web", "production"] | |
monitoring = true | |
vpc_uuid = digitalocean_vpc.vpc.id | |
ssh_keys = data.digitalocean_ssh_keys.keys.ssh_keys[*].id | |
#-----------------------------------------------------------------------------------------------# | |
# Ensures that we create the new resource before we destroy the old one # | |
# https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations # | |
#-----------------------------------------------------------------------------------------------# | |
lifecycle { | |
create_before_destroy = true | |
} | |
} | |
resource "digitalocean_droplet" "worker" { | |
count = 1 | |
image = "docker-20-04" | |
name = "${var.product_name}-${var.digitalocean_region}-worker-${count.index + 1}" | |
region = var.digitalocean_region | |
size = var.droplet_size | |
tags = ["${var.product_name}-worker", "production"] | |
monitoring = true | |
vpc_uuid = digitalocean_vpc.vpc.id | |
ssh_keys = data.digitalocean_ssh_keys.keys.ssh_keys[*].id | |
#-----------------------------------------------------------------------------------------------# | |
# Ensures that we create the new resource before we destroy the old one # | |
# https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations # | |
#-----------------------------------------------------------------------------------------------# | |
lifecycle { | |
create_before_destroy = true | |
} | |
} | |
resource "digitalocean_firewall" "web" { | |
name = "production-firewall" | |
droplet_ids = flatten([digitalocean_droplet.web.*.id, digitalocean_droplet.worker.*.id]) | |
#--------------------------------------------------------------------------# | |
# Internal VPC Rules. We have to let ourselves talk to each other # | |
#--------------------------------------------------------------------------# | |
inbound_rule { | |
protocol = "tcp" | |
port_range = "1-65535" | |
source_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
inbound_rule { | |
protocol = "udp" | |
port_range = "1-65535" | |
source_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
inbound_rule { | |
protocol = "icmp" | |
source_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
inbound_rule { | |
protocol = "tcp" | |
port_range = "22" | |
source_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
inbound_rule { | |
protocol = "tcp" | |
port_range = "80" | |
source_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
outbound_rule { | |
protocol = "udp" | |
port_range = "1-65535" | |
destination_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
outbound_rule { | |
protocol = "tcp" | |
port_range = "1-65535" | |
destination_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
outbound_rule { | |
protocol = "icmp" | |
destination_addresses = [digitalocean_vpc.vpc.ip_range] | |
} | |
#--------------------------------------------------------------------------# | |
# Selective Outbound Traffic Rules # | |
#--------------------------------------------------------------------------# | |
# DNS | |
outbound_rule { | |
protocol = "udp" | |
port_range = "53" | |
destination_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
# HTTP | |
outbound_rule { | |
protocol = "tcp" | |
port_range = "80" | |
destination_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
# HTTPS | |
outbound_rule { | |
protocol = "tcp" | |
port_range = "443" | |
destination_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
outbound_rule { | |
protocol = "tcp" | |
port_range = "587" | |
destination_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
# ICMP (Ping) | |
outbound_rule { | |
protocol = "icmp" | |
destination_addresses = ["0.0.0.0/0", "::/0"] | |
} | |
} | |
resource "digitalocean_loadbalancer" "public" { | |
name = "${var.product_name}-production-loadbalancer-1" | |
region = "${var.digitalocean_region}" | |
forwarding_rule { | |
entry_port = 80 | |
entry_protocol = "http" | |
target_port = 80 | |
target_protocol = "http" | |
} | |
healthcheck { | |
port = 80 | |
protocol = "tcp" | |
} | |
droplet_ids = toset(digitalocean_droplet.web.*.id) | |
vpc_uuid = digitalocean_vpc.vpc.id | |
#-----------------------------------------------------------------------------------------------# | |
# Ensures that we create the new resource before we destroy the old one # | |
# https://www.terraform.io/docs/configuration/resources.html#lifecycle-lifecycle-customizations # | |
#-----------------------------------------------------------------------------------------------# | |
lifecycle { | |
create_before_destroy = true | |
} | |
} | |
resource "digitalocean_database_cluster" "primary" { | |
name = "${var.product_name}-primary-psql-cluster" | |
engine = "pg" | |
version = "15" | |
size = var.database_size | |
region = var.digitalocean_region | |
private_network_uuid = digitalocean_vpc.vpc.id | |
node_count = 1 | |
} | |
resource "digitalocean_database_db" "primary" { | |
cluster_id = digitalocean_database_cluster.primary.id | |
name = "${var.product_name}-primary-db" | |
} | |
resource "digitalocean_database_firewall" "web" { | |
cluster_id = digitalocean_database_cluster.primary.id | |
rule { | |
type = "tag" | |
value = "${var.product_name}-web" | |
} | |
rule { | |
type = "tag" | |
value = "${var.product_name}-worker" | |
} | |
} | |
resource "digitalocean_database_cluster" "redis" { | |
name = "${var.product_name}-primary-redis-cluster" | |
engine = "redis" | |
version = "7" | |
size = var.redis_size | |
region = var.digitalocean_region | |
eviction_policy = "allkeys_lru" | |
private_network_uuid = digitalocean_vpc.vpc.id | |
node_count = 1 | |
} | |
resource "digitalocean_database_firewall" "redis-firewall" { | |
cluster_id = digitalocean_database_cluster.redis.id | |
rule { | |
type = "tag" | |
value = "${var.product_name}-web" | |
} | |
rule { | |
type = "tag" | |
value = "${var.product_name}-worker" | |
} | |
} | |
resource "digitalocean_project" "your-app" { | |
name = "Your App" | |
description = "Your App Production Environment." | |
purpose = "Web Application" | |
environment = "Production" | |
} | |
resource "digitalocean_project_resources" "your-app" { | |
project = digitalocean_project.your-app.id | |
resources = flatten([ | |
digitalocean_droplet.web.*.urn, | |
digitalocean_droplet.worker.*.urn, | |
digitalocean_loadbalancer.public.urn, | |
digitalocean_database_cluster.primary.urn, | |
digitalocean_database_cluster.redis.urn, | |
]) | |
} | |
This file contains 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
terraform { | |
required_version = "~> 1.4.6" | |
required_providers { | |
aws = "~> 3.63" | |
digitalocean = { | |
source = "digitalocean/digitalocean" | |
version = "~> 2.0" | |
} | |
} | |
} | |
provider "aws" { | |
region = "us-west-1" | |
shared_credentials_file = "$HOME/.aws/credentials" | |
profile = "default" | |
skip_region_validation = true | |
} | |
provider "digitalocean" { | |
token = var.do_token | |
} | |
This file contains 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
output "ecr_repo_url" { | |
description = "AWS ECR Repo URL" | |
value = aws_ecr_repository.repo.repository_url | |
} | |
output "web_droplet_ip_address" { | |
description = "Digital Ocean Droplet IPs" | |
value = digitalocean_droplet.web.*.ipv4_address | |
} | |
output "worker_droplet_ip_address" { | |
description = "Digital Ocean Droplet IPs" | |
value = digitalocean_droplet.worker.*.ipv4_address | |
} | |
output "database_cluster_url" { | |
description = "Digital Ocean PGSQL Cluster URL" | |
sensitive = true | |
value = digitalocean_database_cluster.primary.uri | |
} | |
output "redis_cluster_url" { | |
description = "Digital Ocean Redis Cluster URL" | |
sensitive = true | |
value = digitalocean_database_cluster.redis.uri | |
} |
This file contains 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
do_token="<your do token>" | |
ssh_key_names=["<name of ssh key already enabled at DO>"] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment