Treafik woz 'ere
This commit is contained in:
parent
ef40638cd6
commit
618ca06093
57 changed files with 891 additions and 199 deletions
docker
network
service
socket-proxy
volume
products
forgejo/actions-runner
frigate
gitea/actions-runner
github/actions-runner
homeassistant
minio
mitmproxy
pgbackweb
portainer/ui
smokeping
statping
traefik
watchtower
|
@ -7,3 +7,15 @@ variable "labels" {
|
||||||
default = {}
|
default = {}
|
||||||
description = "A map of labels to apply to the service"
|
description = "A map of labels to apply to the service"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
variable "network_name" {
|
||||||
|
type = string
|
||||||
|
description = "Override the automatically selected name of the network"
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "subnet" {
|
||||||
|
type = string
|
||||||
|
description = "The subnet to use for the network."
|
||||||
|
default = null //"172.16.0.0/16"
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
locals {
|
locals {
|
||||||
network_name = var.stack_name
|
// Concat up the network name
|
||||||
|
network_name = var.network_name != null ? "${var.stack_name}-${var.network_name}" : var.stack_name
|
||||||
|
|
||||||
|
// Attach labels
|
||||||
labels = merge(var.labels, {
|
labels = merge(var.labels, {
|
||||||
"com.docker.stack.namespace" = var.stack_name
|
"com.docker.stack.namespace" = var.stack_name
|
||||||
"ooo.grey.network.stack" = var.stack_name
|
"ooo.grey.network.stack" = var.stack_name
|
||||||
"ooo.grey.network.name" = local.network_name
|
"ooo.grey.network.name" = local.network_name
|
||||||
#"ooo.grey.network.created" = timestamp()
|
"ooo.grey.network.subnet" = local.subnet
|
||||||
})
|
})
|
||||||
}
|
}
|
|
@ -1,6 +1,13 @@
|
||||||
resource "docker_network" "instance" {
|
resource "docker_network" "instance" {
|
||||||
name = local.network_name
|
name = local.network_name
|
||||||
driver = "overlay"
|
driver = "overlay"
|
||||||
|
attachable = true
|
||||||
|
ipam_driver = "default"
|
||||||
|
ipam_config {
|
||||||
|
aux_address = {}
|
||||||
|
subnet = local.subnet
|
||||||
|
gateway = local.gateway
|
||||||
|
}
|
||||||
|
|
||||||
# Attach labels
|
# Attach labels
|
||||||
dynamic "labels" {
|
dynamic "labels" {
|
||||||
|
@ -10,4 +17,8 @@ resource "docker_network" "instance" {
|
||||||
value = labels.value
|
value = labels.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
create_before_destroy = false
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,3 +1,9 @@
|
||||||
output "network" {
|
output "network" {
|
||||||
value = docker_network.instance.id
|
value = docker_network.instance.id
|
||||||
|
}
|
||||||
|
output "name" {
|
||||||
|
value = docker_network.instance.name
|
||||||
|
}
|
||||||
|
output "id" {
|
||||||
|
value = docker_network.instance.id
|
||||||
}
|
}
|
14
docker/network/subnet.tf
Normal file
14
docker/network/subnet.tf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
resource "random_integer" "upper_mid_byte" {
|
||||||
|
min = 18
|
||||||
|
max = 31
|
||||||
|
}
|
||||||
|
resource "random_integer" "lower_mid_byte" {
|
||||||
|
min = 10
|
||||||
|
max = 254
|
||||||
|
}
|
||||||
|
locals {
|
||||||
|
// Generate a subnet
|
||||||
|
subnet = var.subnet != null ? var.subnet : "172.${random_integer.upper_mid_byte.result}.${random_integer.lower_mid_byte.result}.0/24"
|
||||||
|
// Calculate the gateway from the subnet
|
||||||
|
gateway = cidrhost(local.subnet, 1)
|
||||||
|
}
|
|
@ -5,6 +5,10 @@ terraform {
|
||||||
source = "kreuzwerker/docker"
|
source = "kreuzwerker/docker"
|
||||||
version = "~>3.0"
|
version = "~>3.0"
|
||||||
}
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~>3.3"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
|
/*
|
||||||
resource "docker_image" "mirror" {
|
resource "docker_image" "mirror" {
|
||||||
count = var.mirror != null ? 1 : 0
|
count = local.enable_mirror ? 1 : 0
|
||||||
name = data.docker_registry_image.image.name
|
name = data.docker_registry_image.image.name
|
||||||
pull_triggers = [data.docker_registry_image.image.sha256_digest]
|
pull_triggers = [data.docker_registry_image.image.sha256_digest]
|
||||||
force_remove = false
|
force_remove = false
|
||||||
}
|
}
|
||||||
resource "docker_tag" "mirror" {
|
resource "docker_tag" "mirror" {
|
||||||
count = var.mirror != null ? 1 : 0
|
count = local.enable_mirror ? 1 : 0
|
||||||
source_image = docker_image.mirror[0].name
|
source_image = docker_image.mirror[0].name
|
||||||
target_image = var.mirror
|
target_image = var.mirror
|
||||||
}
|
}
|
||||||
resource "docker_registry_image" "mirror" {
|
resource "docker_registry_image" "mirror" {
|
||||||
count = var.mirror != null ? 1 : 0
|
count = local.enable_mirror ? 1 : 0
|
||||||
depends_on = [docker_tag.mirror[0]]
|
depends_on = [docker_tag.mirror[0]]
|
||||||
name = docker_tag.mirror[0].target_image
|
name = docker_tag.mirror[0].target_image
|
||||||
keep_remotely = true
|
keep_remotely = true
|
||||||
}
|
}
|
||||||
|
*/
|
|
@ -19,6 +19,11 @@ variable "restart_policy" {
|
||||||
condition = var.restart_policy == "any" || var.restart_policy == "on-failure" || var.restart_policy == "none"
|
condition = var.restart_policy == "any" || var.restart_policy == "on-failure" || var.restart_policy == "none"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
variable "restart_delay" {
|
||||||
|
type = string
|
||||||
|
default = "0s"
|
||||||
|
description = "The delay before restarting the service."
|
||||||
|
}
|
||||||
variable "one_shot" {
|
variable "one_shot" {
|
||||||
type = bool
|
type = bool
|
||||||
default = false
|
default = false
|
||||||
|
@ -53,9 +58,12 @@ variable "volumes" {
|
||||||
description = "A map of volume names to create and mount. The key is the volume name, and the value is the mount point."
|
description = "A map of volume names to create and mount. The key is the volume name, and the value is the mount point."
|
||||||
}
|
}
|
||||||
variable "remote_volumes" {
|
variable "remote_volumes" {
|
||||||
type = map(string)
|
type = map(object({
|
||||||
|
id = string
|
||||||
|
driver = string
|
||||||
|
}))
|
||||||
default = {}
|
default = {}
|
||||||
description = "A map of remote volumes to mount into the container."
|
description = "A map of remote volumes to mount into the container. The key is the source, and the value is the target."
|
||||||
}
|
}
|
||||||
variable "mounts" {
|
variable "mounts" {
|
||||||
type = map(string)
|
type = map(string)
|
||||||
|
@ -76,8 +84,8 @@ variable "ports" {
|
||||||
default = []
|
default = []
|
||||||
description = "A map of port mappings to expose on the host. The key is the host port, and the value is the container port."
|
description = "A map of port mappings to expose on the host. The key is the host port, and the value is the container port."
|
||||||
validation {
|
validation {
|
||||||
error_message = "Host Ports must be between 1024 and 65535."
|
error_message = "Host Ports must be between 1 and 65535."
|
||||||
condition = alltrue([for port in var.ports : port.host >= 1024 && port.host <= 65535])
|
condition = alltrue([for port in var.ports : port.host >= 1 && port.host <= 65535])
|
||||||
}
|
}
|
||||||
validation {
|
validation {
|
||||||
error_message = "Container Ports must be between 1 and 65535."
|
error_message = "Container Ports must be between 1 and 65535."
|
||||||
|
@ -105,6 +113,11 @@ variable "parallelism" {
|
||||||
type = number
|
type = number
|
||||||
description = "The number of instances to run."
|
description = "The number of instances to run."
|
||||||
}
|
}
|
||||||
|
variable "parallelism_per_node" {
|
||||||
|
default = 0
|
||||||
|
type = number
|
||||||
|
description = "The maximum number of instances to run per node. 0 means no limit."
|
||||||
|
}
|
||||||
variable "update_waves" {
|
variable "update_waves" {
|
||||||
default = 3
|
default = 3
|
||||||
type = number
|
type = number
|
||||||
|
@ -144,4 +157,32 @@ variable "converge_timeout" {
|
||||||
default = "2m"
|
default = "2m"
|
||||||
type = string
|
type = string
|
||||||
description = "The timeout for the service to converge."
|
description = "The timeout for the service to converge."
|
||||||
|
}
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
||||||
|
variable "limit_cpu" {
|
||||||
|
default = null
|
||||||
|
type = number
|
||||||
|
description = "The CPU limit for the service."
|
||||||
|
}
|
||||||
|
variable "limit_ram_mb" {
|
||||||
|
default = null
|
||||||
|
type = number
|
||||||
|
description = "The RAM limit for the service, measured in megabytes."
|
||||||
|
}
|
||||||
|
variable "reserved_cpu" {
|
||||||
|
default = null
|
||||||
|
type = number
|
||||||
|
description = "The CPU reservation for the service."
|
||||||
|
}
|
||||||
|
variable "reserved_ram_mb" {
|
||||||
|
default = null
|
||||||
|
type = number
|
||||||
|
description = "The RAM reservation for the service, measured in megabytes."
|
||||||
}
|
}
|
27
docker/service/labels.tf
Normal file
27
docker/service/labels.tf
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
locals {
|
||||||
|
# Define service labels en-masse
|
||||||
|
labels = merge(var.labels, {
|
||||||
|
"com.docker.stack.namespace" = var.stack_name
|
||||||
|
"com.docker.stack.image" = data.docker_registry_image.image.name
|
||||||
|
"ooo.grey.service.stack" = var.stack_name
|
||||||
|
"ooo.grey.service.name" = var.service_name
|
||||||
|
"ooo.grey.service.image" = data.docker_registry_image.image.name
|
||||||
|
"ooo.grey.service.image.digest" = data.docker_registry_image.image.sha256_digest
|
||||||
|
}, local.traefik_labels)
|
||||||
|
|
||||||
|
# Calculate the traefik labels to use if enabled
|
||||||
|
traefik_labels = merge(
|
||||||
|
(var.traefik == null ? {
|
||||||
|
"traefik.enable" = "false"
|
||||||
|
} : {
|
||||||
|
"traefik.enable" = "true"
|
||||||
|
"traefik.http.routers.${local.service_name}.rule" = "Host(`${var.traefik.domain}`)"
|
||||||
|
"traefik.http.routers.${local.service_name}.entrypoints" = "websecure"
|
||||||
|
"traefik.http.routers.${local.service_name}.tls.certresolver" = "default"
|
||||||
|
}),
|
||||||
|
(try(var.traefik.port, null) == null ? {} : {
|
||||||
|
"traefik.http.services.${local.service_name}.loadbalancer.server.port" = var.traefik.port
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -5,16 +5,9 @@ locals {
|
||||||
substr(var.service_name, 0, 63 - 1 - 20),
|
substr(var.service_name, 0, 63 - 1 - 20),
|
||||||
])
|
])
|
||||||
|
|
||||||
# Define service labels en-masse
|
enable_mirror = false // var.mirror != null
|
||||||
labels = merge(var.labels, {
|
|
||||||
"com.docker.stack.namespace" = var.stack_name
|
|
||||||
"com.docker.stack.image" = data.docker_registry_image.image.name
|
|
||||||
"ooo.grey.service.stack" = var.stack_name
|
|
||||||
"ooo.grey.service.name" = var.service_name
|
|
||||||
"ooo.grey.service.image" = data.docker_registry_image.image.name
|
|
||||||
"ooo.grey.service.image.digest" = data.docker_registry_image.image.sha256_digest
|
|
||||||
})
|
|
||||||
|
|
||||||
# Calculate the docker image to use
|
# Calculate the docker image to use
|
||||||
image = var.mirror != null ? "${docker_registry_image.mirror[0].name}@${docker_registry_image.mirror[0].sha256_digest}" : "${data.docker_registry_image.image.name}@${data.docker_registry_image.image.sha256_digest}"
|
#image = local.enable_mirror ? "${docker_registry_image.mirror[0].name}@${docker_registry_image.mirror[0].sha256_digest}" : "${data.docker_registry_image.image.name}@${data.docker_registry_image.image.sha256_digest}"
|
||||||
|
image = "${data.docker_registry_image.image.name}@${data.docker_registry_image.image.sha256_digest}"
|
||||||
}
|
}
|
4
docker/service/networks.tf
Normal file
4
docker/service/networks.tf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
data "docker_network" "networks" {
|
||||||
|
count = var.networks != null ? length(var.networks) : 0
|
||||||
|
name = var.networks[count.index]
|
||||||
|
}
|
|
@ -17,9 +17,10 @@ resource "docker_service" "instance" {
|
||||||
dynamic "mounts" {
|
dynamic "mounts" {
|
||||||
for_each = var.volumes
|
for_each = var.volumes
|
||||||
content {
|
content {
|
||||||
target = mounts.value
|
source = docker_volume.volume[mounts.key].id
|
||||||
source = docker_volume.volume[mounts.key].id
|
target = mounts.value
|
||||||
type = "volume"
|
type = "volume"
|
||||||
|
read_only = false # Nice assumption bro.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +28,10 @@ resource "docker_service" "instance" {
|
||||||
dynamic "mounts" {
|
dynamic "mounts" {
|
||||||
for_each = var.remote_volumes
|
for_each = var.remote_volumes
|
||||||
content {
|
content {
|
||||||
target = mounts.value
|
source = mounts.value.id
|
||||||
source = mounts.key
|
target = mounts.key
|
||||||
type = "volume"
|
type = "volume"
|
||||||
|
read_only = false # Nice assumption bro.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +39,8 @@ resource "docker_service" "instance" {
|
||||||
dynamic "mounts" {
|
dynamic "mounts" {
|
||||||
for_each = var.mounts
|
for_each = var.mounts
|
||||||
content {
|
content {
|
||||||
target = mounts.value
|
|
||||||
source = mounts.key
|
source = mounts.key
|
||||||
|
target = mounts.value
|
||||||
type = "bind"
|
type = "bind"
|
||||||
read_only = false # Nice assumption bro.
|
read_only = false # Nice assumption bro.
|
||||||
}
|
}
|
||||||
|
@ -50,7 +52,7 @@ resource "docker_service" "instance" {
|
||||||
content {
|
content {
|
||||||
config_id = module.config[configs.key].id
|
config_id = module.config[configs.key].id
|
||||||
config_name = module.config[configs.key].name
|
config_name = module.config[configs.key].name
|
||||||
file_name = configs.value
|
file_name = configs.key
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,27 +85,41 @@ resource "docker_service" "instance" {
|
||||||
|
|
||||||
# Apply the networks
|
# Apply the networks
|
||||||
dynamic "networks_advanced" {
|
dynamic "networks_advanced" {
|
||||||
for_each = var.networks
|
for_each = data.docker_network.networks
|
||||||
content {
|
content {
|
||||||
name = networks_advanced.value
|
name = networks_advanced.value.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Apply restart policy
|
# Apply restart policy
|
||||||
restart_policy {
|
restart_policy {
|
||||||
condition = var.one_shot ? "none" : var.restart_policy
|
condition = var.one_shot ? "none" : var.restart_policy
|
||||||
delay = "0s"
|
delay = var.restart_delay
|
||||||
window = "0s"
|
window = "0s"
|
||||||
max_attempts = 0
|
max_attempts = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Apply the placement constraints
|
||||||
placement {
|
placement {
|
||||||
constraints = var.placement_constraints
|
max_replicas = var.parallelism_per_node
|
||||||
|
constraints = var.placement_constraints
|
||||||
platforms {
|
platforms {
|
||||||
architecture = var.processor_architecture
|
architecture = var.processor_architecture
|
||||||
os = var.operating_system
|
os = var.operating_system
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Apply the resource limits and reservations
|
||||||
|
resources {
|
||||||
|
limits {
|
||||||
|
memory_bytes = var.limit_ram_mb != null ? 1024 * 1024 * var.limit_ram_mb : 0
|
||||||
|
nano_cpus = var.limit_cpu != null ? (1000000000 / 100) * var.limit_cpu : 0
|
||||||
|
}
|
||||||
|
reservation {
|
||||||
|
memory_bytes = var.reserved_ram_mb != null ? 1024 * 1024 * var.reserved_ram_mb : 0
|
||||||
|
nano_cpus = var.reserved_cpu != null ? (1000000000 / 100) * var.reserved_cpu : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Global deploy
|
# Global deploy
|
||||||
|
|
|
@ -19,7 +19,8 @@ variable "service_name" {
|
||||||
description = "The name of the service to create."
|
description = "The name of the service to create."
|
||||||
}
|
}
|
||||||
variable "placement_constraints" {
|
variable "placement_constraints" {
|
||||||
default = ["node.role == manager"]
|
default = []
|
||||||
type = list(string)
|
type = list(string)
|
||||||
description = "Docker Swarm placement constraints"
|
description = "Docker Swarm placement constraints"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
6
docker/socket-proxy/output.tf
Normal file
6
docker/socket-proxy/output.tf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.service.docker_service
|
||||||
|
}
|
||||||
|
output "network" {
|
||||||
|
value = module.network.network
|
||||||
|
}
|
|
@ -1,14 +1,17 @@
|
||||||
module "network" {
|
module "network" {
|
||||||
source = "../network"
|
source = "../network"
|
||||||
name = "docker-socket-proxy"
|
network_name = "docker-socket-proxy"
|
||||||
stack_name = var.stack_name
|
stack_name = var.stack_name
|
||||||
}
|
}
|
||||||
module "service" {
|
module "service" {
|
||||||
source = "../service"
|
source = "../service"
|
||||||
image = "${var.docker_socket_proxy_image}:${var.docker_socket_proxy_version}"
|
image = "${var.docker_socket_proxy_image}:${var.docker_socket_proxy_version}"
|
||||||
command = ["/docker-entrypoint.sh", "sockd-username"]
|
stack_name = var.stack_name
|
||||||
stack_name = var.stack_name
|
service_name = var.service_name
|
||||||
service_name = var.service_name
|
placement_constraints = concat(["node.role == manager"], var.placement_constraints)
|
||||||
|
global = true
|
||||||
|
networks = [module.network.network]
|
||||||
|
mounts = { "/var/run/docker.sock" = "/var/run/docker.sock" }
|
||||||
environment_variables = {
|
environment_variables = {
|
||||||
SWARM = 1
|
SWARM = 1
|
||||||
SERVICES = 1
|
SERVICES = 1
|
||||||
|
@ -17,18 +20,4 @@ module "service" {
|
||||||
NODES = 1
|
NODES = 1
|
||||||
NETWORKS = 1
|
NETWORKS = 1
|
||||||
}
|
}
|
||||||
placement_constraints = var.placement_constraints
|
|
||||||
global = true
|
|
||||||
networks = [module.network.network.id]
|
|
||||||
mounts = [
|
|
||||||
{
|
|
||||||
target = "/var/run/docker.sock"
|
|
||||||
source = "/var/run/docker.sock"
|
|
||||||
read_only = false
|
|
||||||
type = "bind"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
output "network" {
|
|
||||||
value = module.network.network
|
|
||||||
}
|
|
|
@ -1,3 +1,6 @@
|
||||||
output "source" {
|
output "source" {
|
||||||
value = docker_volume.volume.id
|
value = docker_volume.volume.id
|
||||||
|
}
|
||||||
|
output "volume" {
|
||||||
|
value = docker_volume.volume
|
||||||
}
|
}
|
|
@ -13,9 +13,7 @@ module "forgejo_actions_runner" {
|
||||||
forgejo_RUNNER_REGISTRATION_TOKEN = var.forgejo_token
|
forgejo_RUNNER_REGISTRATION_TOKEN = var.forgejo_token
|
||||||
CONFIG_FILE = "/config.yaml"
|
CONFIG_FILE = "/config.yaml"
|
||||||
}
|
}
|
||||||
mounts = {
|
mounts = { "/var/run/docker.sock" = "/var/run/docker.sock" }
|
||||||
"/var/run/docker.sock" = "/var/run/docker.sock"
|
|
||||||
}
|
|
||||||
configs = {
|
configs = {
|
||||||
forgejo-config = yamlencode({
|
forgejo-config = yamlencode({
|
||||||
name_prefix = ["forgejo-config", var.stack_name, var.service_name]
|
name_prefix = ["forgejo-config", var.stack_name, var.service_name]
|
||||||
|
|
55
products/frigate/frigate.tf
Normal file
55
products/frigate/frigate.tf
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
data "docker_registry_image" "frigate" {
|
||||||
|
name = "ghcr.io/blakeblackshear/frigate:stable"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "docker_container" "frigate" {
|
||||||
|
image = "${data.docker_registry_image.frigate.name}@${data.docker_registry_image.frigate.sha256_digest}"
|
||||||
|
name = local.container_name
|
||||||
|
restart = "unless-stopped"
|
||||||
|
privileged = "true"
|
||||||
|
shm_size = var.shm_size_mb
|
||||||
|
network_mode = "bridge"
|
||||||
|
env = [
|
||||||
|
"FRIGATE_RTSP_PASSWORD=${var.frigate_rtsp_password}"
|
||||||
|
]
|
||||||
|
dynamic "devices" {
|
||||||
|
for_each = var.devices
|
||||||
|
content {
|
||||||
|
host_path = devices.value.host_path
|
||||||
|
container_path = devices.value.container_path
|
||||||
|
permissions = devices.value.permissions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic "volumes" {
|
||||||
|
for_each = var.volumes
|
||||||
|
content {
|
||||||
|
container_path = volumes.value
|
||||||
|
host_path = volumes.key
|
||||||
|
read_only = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic "ports" {
|
||||||
|
for_each = var.ports
|
||||||
|
content {
|
||||||
|
internal = ports.value.container
|
||||||
|
external = ports.value.host
|
||||||
|
protocol = ports.value.protocol
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic "networks_advanced" {
|
||||||
|
for_each = var.networks
|
||||||
|
content {
|
||||||
|
name = networks_advanced.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dynamic "labels" {
|
||||||
|
for_each = local.labels
|
||||||
|
content {
|
||||||
|
label = labels.key
|
||||||
|
value = labels.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lifecycle {
|
||||||
|
create_before_destroy = false
|
||||||
|
}
|
||||||
|
}
|
82
products/frigate/inputs.tf
Normal file
82
products/frigate/inputs.tf
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
variable "stack_name" {
|
||||||
|
type = string
|
||||||
|
description = "The name of the stack to deploy the service to."
|
||||||
|
}
|
||||||
|
variable "shm_size_mb" {
|
||||||
|
default = 256
|
||||||
|
type = number
|
||||||
|
description = "The size of the shared memory segment in MB"
|
||||||
|
}
|
||||||
|
variable "networks" {
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
description = "A list of network names to attach the service to."
|
||||||
|
}
|
||||||
|
variable "frigate_rtsp_password" {
|
||||||
|
type = string
|
||||||
|
description = "The password to use for the RTSP streams"
|
||||||
|
default = ""
|
||||||
|
}
|
||||||
|
variable "devices" {
|
||||||
|
type = list(object({
|
||||||
|
host_path = string
|
||||||
|
container_path = string
|
||||||
|
permissions = optional(string, "rwm")
|
||||||
|
}))
|
||||||
|
description = "The devices to mount into the container"
|
||||||
|
}
|
||||||
|
variable "volumes" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
description = "A map of volume names to create and mount. The key is the volume name, and the value is the mount point."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "ports" {
|
||||||
|
type = list(object({
|
||||||
|
host = number
|
||||||
|
container = number
|
||||||
|
protocol = optional(string, "tcp")
|
||||||
|
}))
|
||||||
|
default = [
|
||||||
|
{
|
||||||
|
container = 5000
|
||||||
|
host = 5000
|
||||||
|
protocol = "tcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container = 1935
|
||||||
|
host = 1935
|
||||||
|
protocol = "tcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container = 8554
|
||||||
|
host = 8554
|
||||||
|
protocol = "tcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container = 8555
|
||||||
|
host = 8555
|
||||||
|
protocol = "tcp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
container = 8555
|
||||||
|
host = 8555
|
||||||
|
protocol = "udp"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number, 5000)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "labels" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
description = "A map of labels to apply to the service"
|
||||||
|
}
|
24
products/frigate/labels.tf
Normal file
24
products/frigate/labels.tf
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
|
||||||
|
locals {
|
||||||
|
container_name = "frigate"
|
||||||
|
# Define service labels en-masse
|
||||||
|
labels = merge({
|
||||||
|
"com.docker.stack.namespace" = var.stack_name
|
||||||
|
"com.docker.stack.image" = data.docker_registry_image.frigate.name
|
||||||
|
"ooo.grey.service.stack" = var.stack_name
|
||||||
|
"ooo.grey.service.name" = local.container_name
|
||||||
|
"ooo.grey.service.image" = data.docker_registry_image.frigate.name
|
||||||
|
"ooo.grey.service.image.digest" = data.docker_registry_image.frigate.sha256_digest
|
||||||
|
}, local.traefik_labels, var.labels)
|
||||||
|
|
||||||
|
# Calculate the traefik labels to use if enabled
|
||||||
|
traefik_labels = var.traefik != null ? {
|
||||||
|
"traefik.enable" = "true"
|
||||||
|
"traefik.http.routers.${local.container_name}.rule" = "Host(`${var.traefik.domain}`)"
|
||||||
|
"traefik.http.routers.${local.container_name}.entrypoints" = "websecure"
|
||||||
|
"traefik.http.routers.${local.container_name}.tls.certresolver" = "default"
|
||||||
|
"traefik.http.services.${local.container_name}.loadbalancer.server.port" = 5000
|
||||||
|
} : {
|
||||||
|
"traefik.enable" = "false"
|
||||||
|
}
|
||||||
|
}
|
3
products/frigate/output.tf
Normal file
3
products/frigate/output.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "endpoint" {
|
||||||
|
value = try("https://${var.traefik.domain}", "unknown")
|
||||||
|
}
|
11
products/frigate/terraform.tf
Normal file
11
products/frigate/terraform.tf
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,7 @@ module "gitea_actions_runner" {
|
||||||
GITEA_RUNNER_REGISTRATION_TOKEN = var.gitea_token
|
GITEA_RUNNER_REGISTRATION_TOKEN = var.gitea_token
|
||||||
CONFIG_FILE = "/config.yaml"
|
CONFIG_FILE = "/config.yaml"
|
||||||
}
|
}
|
||||||
mounts = {
|
mounts = { "/var/run/docker.sock" = "/var/run/docker.sock" }
|
||||||
"/var/run/docker.sock" = "/var/run/docker.sock"
|
|
||||||
}
|
|
||||||
configs = {
|
configs = {
|
||||||
gitea-config = {
|
gitea-config = {
|
||||||
name_prefix = ["gitea-config", var.stack_name, var.service_name]
|
name_prefix = ["gitea-config", var.stack_name, var.service_name]
|
||||||
|
|
|
@ -15,7 +15,5 @@ module "github_actions_runner" {
|
||||||
EPHEMERAL = true
|
EPHEMERAL = true
|
||||||
DISABLE_AUTO_UPDATE = "disable_updates"
|
DISABLE_AUTO_UPDATE = "disable_updates"
|
||||||
}
|
}
|
||||||
mounts = {
|
mounts = { "/var/run/docker.sock" = "/var/run/docker.sock" }
|
||||||
"/var/run/docker.sock" = "/var/run/docker.sock"
|
|
||||||
}
|
|
||||||
}
|
}
|
13
products/homeassistant/homeassistant.tf
Normal file
13
products/homeassistant/homeassistant.tf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
module "homeassistant" {
|
||||||
|
source = "../../docker/service"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
service_name = "homeassistant"
|
||||||
|
image = var.default_image
|
||||||
|
environment_variables = merge({ TZ = "Europe/London" }, var.environment_variables)
|
||||||
|
mounts = var.mounts
|
||||||
|
networks = var.networks
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
ports = [{ host = 8123, container = 8123 }]
|
||||||
|
traefik = var.traefik
|
||||||
|
converge_timeout = "5m"
|
||||||
|
}
|
35
products/homeassistant/inputs.tf
Normal file
35
products/homeassistant/inputs.tf
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
variable "stack_name" {
|
||||||
|
default = "homeassistant"
|
||||||
|
type = string
|
||||||
|
description = "The name of the stack to create."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "default_image" {
|
||||||
|
default = "ghcr.io/home-assistant/home-assistant:stable"
|
||||||
|
type = string
|
||||||
|
description = "The image to use for the homeassistant service"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "environment_variables" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
description = "A map of environment variables to set in the container."
|
||||||
|
}
|
||||||
|
variable "mounts" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
description = "A map of host paths to container paths to mount. The key is the host path, and the value is the container path."
|
||||||
|
}
|
||||||
|
variable "networks" {
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
description = "A list of network names to attach the service to."
|
||||||
|
}
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
||||||
|
variable "traefik" {
|
||||||
|
|
||||||
|
}
|
3
products/homeassistant/outputs.tf
Normal file
3
products/homeassistant/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.homeassistant.docker_service
|
||||||
|
}
|
15
products/homeassistant/terraform.tf
Normal file
15
products/homeassistant/terraform.tf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~> 3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
24
products/minio/inputs.tf
Normal file
24
products/minio/inputs.tf
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
variable "stack_name" {
|
||||||
|
default = "mitmproxy"
|
||||||
|
type = string
|
||||||
|
description = "The name of the stack to create."
|
||||||
|
}
|
||||||
|
variable "networks" {
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
description = "A list of network names to attach the service to."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
64
products/minio/minio.tf
Normal file
64
products/minio/minio.tf
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
resource "random_pet" "minio_admin_user" {
|
||||||
|
length = 2
|
||||||
|
separator = ""
|
||||||
|
}
|
||||||
|
resource "random_password" "minio_admin_password" {
|
||||||
|
length = 32
|
||||||
|
special = false
|
||||||
|
}
|
||||||
|
variable "domain" {
|
||||||
|
type = string
|
||||||
|
description = "The domain to use for the service."
|
||||||
|
}
|
||||||
|
variable "mounts" {
|
||||||
|
type = map(string)
|
||||||
|
default = {}
|
||||||
|
description = "A map of host paths to container paths to mount. The key is the host path, and the value is the container path."
|
||||||
|
}
|
||||||
|
module "minio" {
|
||||||
|
source = "../../docker/service"
|
||||||
|
stack_name = "minio"
|
||||||
|
service_name = "minio"
|
||||||
|
image = "quay.io/minio/minio:latest"
|
||||||
|
command = ["minio", "server", "/data", ]
|
||||||
|
environment_variables = {
|
||||||
|
MINIO_ADDRESS = "0.0.0.0:9000"
|
||||||
|
MINIO_CONSOLE_ADDRESS = "0.0.0.0:9001"
|
||||||
|
MINIO_ROOT_USER = random_pet.minio_admin_user.id
|
||||||
|
MINIO_ROOT_PASSWORD = random_password.minio_admin_password.result
|
||||||
|
MINIO_SERVER_URL = "https://s3.grey.ooo"
|
||||||
|
MINIO_BROWSER_REDIRECT_URL = "https://s3.grey.ooo/ui/"
|
||||||
|
MINIO_BROWSER_REDIRECT = true
|
||||||
|
MINIO_API_ROOT_ACCESS = "on"
|
||||||
|
}
|
||||||
|
mounts = var.mounts
|
||||||
|
networks = concat(["loadbalancer-traefik"], var.networks)
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
labels = {
|
||||||
|
"traefik.enable" = "true"
|
||||||
|
|
||||||
|
// API redirect
|
||||||
|
"traefik.http.routers.minio_api.rule" = "Host(`${var.domain}`) && !PathPrefix(`/ui`)"
|
||||||
|
#"traefik.http.routers.minio_api.service" = "minio_api"
|
||||||
|
"traefik.http.routers.minio_api.entrypoints" = "websecure"
|
||||||
|
"traefik.http.routers.minio_api.tls.certresolver" = "default"
|
||||||
|
"traefik.http.services.minio_api.loadbalancer.server.port" = "9000"
|
||||||
|
|
||||||
|
// UI redirect
|
||||||
|
"traefik.http.routers.minio_ui.rule" = "Host(`${var.domain}`) && PathPrefix(`/ui`)"
|
||||||
|
#"traefik.http.routers.minio_ui.service" = "minio_ui"
|
||||||
|
"traefik.http.routers.minio_ui.entrypoints" = "websecure"
|
||||||
|
"traefik.http.routers.minio_ui.tls.certresolver" = "default"
|
||||||
|
"traefik.http.services.minio_ui.loadbalancer.server.port" = "9001"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "minio" {
|
||||||
|
value = {
|
||||||
|
endpoint = "https://${var.domain}/ui/"
|
||||||
|
auth = {
|
||||||
|
username = module.minio.docker_service.task_spec[0].container_spec[0].env.MINIO_ROOT_USER
|
||||||
|
password = nonsensitive(module.minio.docker_service.task_spec[0].container_spec[0].env.MINIO_ROOT_PASSWORD)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
products/minio/outputs.tf
Normal file
3
products/minio/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.minio.docker_service
|
||||||
|
}
|
15
products/minio/terraform.tf
Normal file
15
products/minio/terraform.tf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~> 3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
25
products/mitmproxy/inputs.tf
Normal file
25
products/mitmproxy/inputs.tf
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
variable "stack_name" {
|
||||||
|
default = "mitmproxy"
|
||||||
|
type = string
|
||||||
|
description = "The name of the stack to create."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "networks" {
|
||||||
|
type = list(string)
|
||||||
|
default = []
|
||||||
|
description = "A list of network names to attach the service to."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number, 8081)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
30
products/mitmproxy/mitmproxy.tf
Normal file
30
products/mitmproxy/mitmproxy.tf
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
module "mitmproxy" {
|
||||||
|
source = "../../docker/service"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
service_name = "mitmproxy"
|
||||||
|
networks = var.networks
|
||||||
|
image = "ghcr.io/benzine-framework/mitmproxy"
|
||||||
|
command = [
|
||||||
|
"mitmweb",
|
||||||
|
"--web-host", "0.0.0.0",
|
||||||
|
"--web-port", "8081",
|
||||||
|
#"--listen-host", "0.0.0.0",
|
||||||
|
#"--listen-port", "8080",
|
||||||
|
#"--ssl-insecure",
|
||||||
|
]
|
||||||
|
healthcheck = ["CMD-SHELL", " curl -I http://localhost:8081 || exit 1"]
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
traefik = var.traefik
|
||||||
|
ports = [
|
||||||
|
{
|
||||||
|
protocol = "tcp"
|
||||||
|
container = 8080
|
||||||
|
host = 4080
|
||||||
|
},
|
||||||
|
{
|
||||||
|
protocol = "tcp"
|
||||||
|
container = 8081
|
||||||
|
host = 4081
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
3
products/mitmproxy/outputs.tf
Normal file
3
products/mitmproxy/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.mitmproxy.docker_service
|
||||||
|
}
|
11
products/mitmproxy/terraform.tf
Normal file
11
products/mitmproxy/terraform.tf
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,4 +26,8 @@ variable "placement_constraints" {
|
||||||
variable "networks" {
|
variable "networks" {
|
||||||
type = list(string)
|
type = list(string)
|
||||||
default = []
|
default = []
|
||||||
|
}
|
||||||
|
variable "domain" {
|
||||||
|
type = string
|
||||||
|
description = "The domain to use for the service's traefik configuration."
|
||||||
}
|
}
|
|
@ -17,6 +17,7 @@ module "pgbackweb" {
|
||||||
service_name = var.service_name
|
service_name = var.service_name
|
||||||
networks = concat([module.network.network], var.networks)
|
networks = concat([module.network.network], var.networks)
|
||||||
placement_constraints = var.placement_constraints
|
placement_constraints = var.placement_constraints
|
||||||
|
traefik = { domain = var.domain }
|
||||||
}
|
}
|
||||||
module "postgres" {
|
module "postgres" {
|
||||||
source = "../postgres"
|
source = "../postgres"
|
||||||
|
|
|
@ -1,16 +1,33 @@
|
||||||
variable "docker" {
|
variable "stack_name" {
|
||||||
type = object({
|
default = "loadbalancer"
|
||||||
name = string
|
type = string
|
||||||
stack_name = optional(string)
|
description = "The name of the stack to create."
|
||||||
networks = list(object({
|
|
||||||
name = string
|
|
||||||
id = string
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
variable "portainer" {
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
type = object({
|
type = object({
|
||||||
version = string
|
domain = string
|
||||||
logo = optional(string)
|
port = optional(number, 9000)
|
||||||
})
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
}
|
}
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "portainer_version" {
|
||||||
|
default = "sts"
|
||||||
|
type = string
|
||||||
|
description = "The version of the portainer image to use."
|
||||||
|
}
|
||||||
|
variable "portainer_logo" {
|
||||||
|
default = null
|
||||||
|
type = string
|
||||||
|
description = "The URL of the logo to use for the portainer service."
|
||||||
|
}
|
||||||
|
variable "should_mount_local_docker_socket" {
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
}
|
|
@ -4,6 +4,6 @@ output "portainer" {
|
||||||
username = "admin" # Sorry, this is hardcoded in the portainer image
|
username = "admin" # Sorry, this is hardcoded in the portainer image
|
||||||
password = nonsensitive(random_password.password.result)
|
password = nonsensitive(random_password.password.result)
|
||||||
}
|
}
|
||||||
service_name = docker_service.portainer.name
|
service_name = module.portainer.docker_service.name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,99 +8,31 @@ resource "htpasswd_password" "hash" {
|
||||||
password = random_password.password.result
|
password = random_password.password.result
|
||||||
salt = random_password.salt.result
|
salt = random_password.salt.result
|
||||||
}
|
}
|
||||||
data "docker_registry_image" "portainer_app" {
|
module "vol_portainer" {
|
||||||
name = "portainer/portainer-ce:${var.portainer.version}"
|
source = "../../../docker/volume"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
volume_name = "portainer"
|
||||||
}
|
}
|
||||||
resource "docker_volume" "portainer" {
|
module "portainer" {
|
||||||
name = var.docker.name
|
source = "../../../docker/service"
|
||||||
}
|
stack_name = var.stack_name
|
||||||
resource "docker_service" "portainer" {
|
service_name = "portainer"
|
||||||
name = var.docker.name
|
image = "portainer/portainer-ce:${var.portainer_version}"
|
||||||
mode {
|
command = [
|
||||||
replicated {
|
"/portainer",
|
||||||
replicas = 1
|
//"--edge-compute",
|
||||||
}
|
"--logo", coalesce(var.portainer_logo),
|
||||||
}
|
"--admin-password", htpasswd_password.hash.bcrypt,
|
||||||
task_spec {
|
]
|
||||||
container_spec {
|
remote_volumes = {
|
||||||
image = "${data.docker_registry_image.portainer_app.name}@${data.docker_registry_image.portainer_app.sha256_digest}"
|
"/data" = module.vol_portainer.volume
|
||||||
command = [
|
}
|
||||||
"/portainer",
|
traefik = var.traefik
|
||||||
//"--edge-compute",
|
mounts = var.should_mount_local_docker_socket ? { "/var/run/docker.sock" = "/var/run/docker.sock" } : {}
|
||||||
"--logo", coalesce(var.portainer.logo),
|
networks = ["loadbalancer-traefik"]
|
||||||
"--admin-password", htpasswd_password.hash.bcrypt,
|
start_first = false
|
||||||
]
|
placement_constraints = concat([
|
||||||
#mounts {
|
"node.role == manager",
|
||||||
# target = "/data"
|
"node.platform.os == linux",
|
||||||
# source = "/portainer"
|
], var.placement_constraints)
|
||||||
# read_only = false
|
|
||||||
# type = "bind"
|
|
||||||
#}
|
|
||||||
mounts {
|
|
||||||
target = "/data"
|
|
||||||
source = docker_volume.portainer.name
|
|
||||||
type = "volume"
|
|
||||||
read_only = false
|
|
||||||
}
|
|
||||||
#mounts {
|
|
||||||
# target = "/var/run/docker.sock"
|
|
||||||
# source = "/var/run/docker.sock"
|
|
||||||
# read_only = false
|
|
||||||
# type = "bind"
|
|
||||||
#}
|
|
||||||
labels {
|
|
||||||
label = "com.docker.stack.namespace"
|
|
||||||
value = var.docker.stack_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dynamic "networks_advanced" {
|
|
||||||
for_each = var.docker.networks
|
|
||||||
content {
|
|
||||||
name = networks_advanced.value.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
restart_policy {
|
|
||||||
condition = "on-failure"
|
|
||||||
delay = "3s"
|
|
||||||
max_attempts = 4
|
|
||||||
window = "10s"
|
|
||||||
}
|
|
||||||
placement {
|
|
||||||
constraints = [
|
|
||||||
"node.role == manager",
|
|
||||||
"node.platform.os == linux",
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endpoint_spec {
|
|
||||||
# ports {
|
|
||||||
# target_port = 9000
|
|
||||||
# publish_mode = "ingress"
|
|
||||||
# published_port = 9000
|
|
||||||
# }
|
|
||||||
# ports {
|
|
||||||
# target_port = 8000
|
|
||||||
# publish_mode = "ingress"
|
|
||||||
# published_port = 8000
|
|
||||||
# }
|
|
||||||
#}
|
|
||||||
update_config {
|
|
||||||
# Portainer gets super fuckin' upset if you start a second instance while the first is holding the db lock
|
|
||||||
order = "stop-first"
|
|
||||||
}
|
|
||||||
|
|
||||||
labels {
|
|
||||||
label = "com.docker.stack.namespace"
|
|
||||||
value = var.docker.stack_name
|
|
||||||
}
|
|
||||||
labels {
|
|
||||||
label = "com.docker.stack.image"
|
|
||||||
value = replace(data.docker_registry_image.portainer_app.name, "/:.*/", "")
|
|
||||||
}
|
|
||||||
lifecycle {
|
|
||||||
ignore_changes = [
|
|
||||||
# MB: This is a hack because terraform keeps detecting a "change" in the placement->platform constraint that doesn't exist.
|
|
||||||
task_spec[0].placement[0].platforms
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
18
products/smokeping/inputs.tf
Normal file
18
products/smokeping/inputs.tf
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
variable "timezone" {
|
||||||
|
type = string
|
||||||
|
description = "The timezone to use for the service."
|
||||||
|
default = "Europe/London"
|
||||||
|
}
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
3
products/smokeping/outputs.tf
Normal file
3
products/smokeping/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.smokeping.docker_service
|
||||||
|
}
|
15
products/smokeping/smokeping.tf
Normal file
15
products/smokeping/smokeping.tf
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module "smokeping" {
|
||||||
|
source = "../../docker/service"
|
||||||
|
stack_name = "smokeping"
|
||||||
|
service_name = "smokeping"
|
||||||
|
image = "linuxserver/smokeping:latest"
|
||||||
|
volumes = { "smokeping" = "/data" }
|
||||||
|
environment_variables = {
|
||||||
|
PUID = 1000
|
||||||
|
PGID = 1000
|
||||||
|
TZ = var.timezone
|
||||||
|
}
|
||||||
|
traefik = var.traefik
|
||||||
|
networks = ["loadbalancer-traefik"]
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
}
|
16
products/smokeping/terraform.tf
Normal file
16
products/smokeping/terraform.tf
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~> 3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -42,3 +42,11 @@ variable "extra_environment_variables" {
|
||||||
default = {}
|
default = {}
|
||||||
description = "Extra environment variables to pass to the service."
|
description = "Extra environment variables to pass to the service."
|
||||||
}
|
}
|
||||||
|
variable "traefik" {
|
||||||
|
default = null
|
||||||
|
type = object({
|
||||||
|
domain = string
|
||||||
|
port = optional(number)
|
||||||
|
})
|
||||||
|
description = "Whether to enable traefik for the service."
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
variable "nginx_hostname" {
|
|
||||||
type = string
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
variable "acme_certificate" {
|
|
||||||
type = object({
|
|
||||||
private_key_pem = string
|
|
||||||
certificate_pem = string
|
|
||||||
issuer_pem = string
|
|
||||||
})
|
|
||||||
default = null
|
|
||||||
}
|
|
||||||
module "nginx_config" {
|
|
||||||
count = var.nginx_hostname != null ? 1 : 0
|
|
||||||
source = "../nginx/site-available"
|
|
||||||
service_name = module.service.service_name
|
|
||||||
hostname = var.nginx_hostname
|
|
||||||
upstream_host = "${module.service.service_name}:8080"
|
|
||||||
config_prefix = module.service.service_name
|
|
||||||
certificate = var.acme_certificate
|
|
||||||
}
|
|
||||||
output "nginx_files" {
|
|
||||||
value = var.nginx_hostname != null ? module.nginx_config[0].files : []
|
|
||||||
}
|
|
|
@ -7,7 +7,7 @@ output "statping" {
|
||||||
port = module.postgres.ports[0]
|
port = module.postgres.ports[0]
|
||||||
}
|
}
|
||||||
statping = {
|
statping = {
|
||||||
instance = var.nginx_hostname != null ? "https://${var.nginx_hostname}" : null
|
instance = try("https://${var.traefik.domain}", "unknown")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ module "service" {
|
||||||
image = "${var.statping_image}:${var.statping_version}"
|
image = "${var.statping_image}:${var.statping_version}"
|
||||||
stack_name = var.stack_name
|
stack_name = var.stack_name
|
||||||
service_name = "statping"
|
service_name = "statping"
|
||||||
networks = concat([module.network.network, ], var.networks)
|
networks = concat([module.network.network, "loadbalancer-traefik"], var.networks)
|
||||||
environment_variables = merge({
|
environment_variables = merge({
|
||||||
VIRTUAL_HOST = "localhost"
|
VIRTUAL_HOST = "localhost"
|
||||||
VIRTUAL_PORT = "8080"
|
VIRTUAL_PORT = "8080"
|
||||||
|
@ -29,4 +29,5 @@ module "service" {
|
||||||
}, var.extra_environment_variables)
|
}, var.extra_environment_variables)
|
||||||
placement_constraints = var.placement_constraints
|
placement_constraints = var.placement_constraints
|
||||||
dns_nameservers = var.dns_nameservers
|
dns_nameservers = var.dns_nameservers
|
||||||
|
traefik = var.traefik
|
||||||
}
|
}
|
||||||
|
|
5
products/traefik/docker-socket-proxy.tf
Normal file
5
products/traefik/docker-socket-proxy.tf
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module "docker_socket_proxy" {
|
||||||
|
source = "../../docker/socket-proxy"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
}
|
14
products/traefik/hello.tf
Normal file
14
products/traefik/hello.tf
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
module "traefik_hello" {
|
||||||
|
count = var.hello_service_domain != null ? 1 : 0
|
||||||
|
source = "../../docker/service"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
service_name = "hello"
|
||||||
|
image = "traefik/whoami"
|
||||||
|
parallelism = 3
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
networks = [module.traefik_network.network, ]
|
||||||
|
traefik = {
|
||||||
|
domain = var.hello_service_domain
|
||||||
|
port = 80
|
||||||
|
}
|
||||||
|
}
|
43
products/traefik/inputs.tf
Normal file
43
products/traefik/inputs.tf
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
variable "stack_name" {
|
||||||
|
default = "loadbalancer"
|
||||||
|
type = string
|
||||||
|
description = "The name of the stack to create."
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "placement_constraints" {
|
||||||
|
default = []
|
||||||
|
type = list(string)
|
||||||
|
description = "Docker Swarm placement constraints"
|
||||||
|
}
|
||||||
|
variable "acme_use_staging" {
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
description = "Whether to use the Let's Encrypt staging server."
|
||||||
|
}
|
||||||
|
variable "acme_email" {
|
||||||
|
description = "The email address to use for the ACME certificate."
|
||||||
|
type = string
|
||||||
|
}
|
||||||
|
variable "traefik_service_domain" {
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
variable "hello_service_domain" {
|
||||||
|
type = string
|
||||||
|
default = null
|
||||||
|
}
|
||||||
|
variable "log_level" {
|
||||||
|
type = string
|
||||||
|
default = "INFO"
|
||||||
|
description = "The log level to use for traefik."
|
||||||
|
}
|
||||||
|
variable "access_log" {
|
||||||
|
type = bool
|
||||||
|
default = false
|
||||||
|
description = "Whether to enable access logging."
|
||||||
|
}
|
||||||
|
variable "redirect_to_ssl" {
|
||||||
|
type = bool
|
||||||
|
default = true
|
||||||
|
description = "Whether to redirect HTTP to HTTPS."
|
||||||
|
}
|
6
products/traefik/network.tf
Normal file
6
products/traefik/network.tf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
module "traefik_network" {
|
||||||
|
source = "../../docker/network"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
network_name = "traefik"
|
||||||
|
subnet = "172.16.0.0/22"
|
||||||
|
}
|
6
products/traefik/outputs.tf
Normal file
6
products/traefik/outputs.tf
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.traefik.docker_service
|
||||||
|
}
|
||||||
|
output "docker_network" {
|
||||||
|
value = module.traefik_network
|
||||||
|
}
|
13
products/traefik/terraform.tf
Normal file
13
products/traefik/terraform.tf
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
terraform {
|
||||||
|
required_version = "~> 1.6"
|
||||||
|
required_providers {
|
||||||
|
docker = {
|
||||||
|
source = "kreuzwerker/docker"
|
||||||
|
version = "~>3.0"
|
||||||
|
}
|
||||||
|
random = {
|
||||||
|
source = "hashicorp/random"
|
||||||
|
version = "~> 3.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
69
products/traefik/traefik.tf
Normal file
69
products/traefik/traefik.tf
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
module "traefik_certs_volume" {
|
||||||
|
source = "../../docker/volume"
|
||||||
|
stack_name = var.stack_name
|
||||||
|
volume_name = "traefik_certs"
|
||||||
|
}
|
||||||
|
module "traefik" {
|
||||||
|
source = "../../docker/service"
|
||||||
|
depends_on = [module.docker_socket_proxy]
|
||||||
|
stack_name = var.stack_name
|
||||||
|
service_name = "traefik"
|
||||||
|
image = "traefik:v3.2"
|
||||||
|
networks = [module.traefik_network.network, module.docker_socket_proxy.network, ]
|
||||||
|
mounts = { "/goliath/letsencrypt" = "/certs" }
|
||||||
|
placement_constraints = var.placement_constraints
|
||||||
|
converge_enable = false // @todo add healthcheck
|
||||||
|
command = [
|
||||||
|
"/usr/local/bin/traefik",
|
||||||
|
"--api.insecure=true",
|
||||||
|
"--api.dashboard=true",
|
||||||
|
"--log.level=${var.log_level}",
|
||||||
|
"--accesslog=${var.access_log ? "true" : "false"}",
|
||||||
|
"--ping=true",
|
||||||
|
|
||||||
|
# Confirm Docker Provider
|
||||||
|
"--providers.docker=true",
|
||||||
|
"--providers.docker.exposedbydefault=false",
|
||||||
|
"--providers.docker.network=${module.traefik_network.name}",
|
||||||
|
"--providers.docker.endpoint=http://${module.docker_socket_proxy.docker_service.name}:2375",
|
||||||
|
|
||||||
|
# Confirm Swarm Provider
|
||||||
|
"--providers.swarm=true",
|
||||||
|
"--providers.swarm.exposedByDefault=false",
|
||||||
|
"--providers.swarm.network=${module.traefik_network.name}",
|
||||||
|
"--providers.swarm.endpoint=http://${module.docker_socket_proxy.docker_service.name}:2375",
|
||||||
|
|
||||||
|
# Configure HTTP and redirect to HTTPS
|
||||||
|
"--entrypoints.web.address=:80",
|
||||||
|
|
||||||
|
# Configure HTTPS
|
||||||
|
"--entrypoints.websecure.address=:443",
|
||||||
|
var.redirect_to_ssl ? "--entrypoints.web.http.redirections.entrypoint.to=websecure" : "",
|
||||||
|
var.redirect_to_ssl ? "--entrypoints.web.http.redirections.entrypoint.scheme=https" : "",
|
||||||
|
|
||||||
|
# Configure the acme provider
|
||||||
|
"--certificatesresolvers.default.acme.tlschallenge=true",
|
||||||
|
var.acme_use_staging ? "--certificatesresolvers.default.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" : "",
|
||||||
|
"--certificatesresolvers.default.acme.email=${var.acme_email}",
|
||||||
|
"--certificatesresolvers.default.acme.storage=/certs/acme.json",
|
||||||
|
]
|
||||||
|
traefik = var.traefik_service_domain != null ? {
|
||||||
|
domain = var.traefik_service_domain
|
||||||
|
port = 8080
|
||||||
|
} : null
|
||||||
|
ports = [
|
||||||
|
{
|
||||||
|
host = 80
|
||||||
|
container = 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host = 443
|
||||||
|
container = 443
|
||||||
|
},
|
||||||
|
{
|
||||||
|
host = 8080
|
||||||
|
container = 8080
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
3
products/watchtower/outputs.tf
Normal file
3
products/watchtower/outputs.tf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
output "docker_service" {
|
||||||
|
value = module.watchtower.docker_service
|
||||||
|
}
|
Loading…
Reference in a new issue