Initial Commit
Some checks failed
Deploy / Update K8s Apps / Detect changed K8s tfvars (push) Failing after 13s
Deploy / Update Apps / Detect changed tfvars files (push) Failing after 13s
Test / Static Analysis (push) Failing after 11s
Test / Unit Tests — Docker Stack (push) Has been skipped
Test / Unit Tests — K8s Stack (push) Has been skipped
Deploy / Update K8s Apps / Deploy ${{ matrix.tfvars }} (push) Has been skipped
Deploy / Update K8s Apps / Destroy ${{ matrix.tfvars }} (push) Has been skipped
Deploy / Update Apps / Deploy ${{ matrix.tfvars }} (push) Has been skipped
Deploy / Update Apps / Destroy ${{ matrix.tfvars }} (push) Has been skipped
Test / Integration Test — K8s (k3d) (push) Has been skipped

This commit is contained in:
2026-03-06 19:17:15 +01:00
commit 3bf5960302
36 changed files with 5859 additions and 0 deletions

View File

@@ -0,0 +1,180 @@
locals {
persistent = var.environment == "prod"
# Inject oauth2 token into git URL for private repos
git_repo_url = (
var.openresty_git_token != ""
? replace(var.openresty_git_repo, "://", "://oauth2:${var.openresty_git_token}@")
: var.openresty_git_repo
)
# Entrypoint for git_clone mode: installs git via apk, clones the pinned ref,
# then hands off to openresty. The cloned repo must have an 'openresty/' subdirectory
# containing a valid nginx.conf (used as the -p prefix path).
git_clone_entrypoint = [
"/bin/sh", "-c",
"apk add --no-cache git && git clone --depth 1 --branch '${var.openresty_git_ref}' '${local.git_repo_url}' /tmp/app && exec openresty -g 'daemon off;' -p /tmp/app/openresty"
]
}
# ─── Network ──────────────────────────────────────────────────────────────────
# Each app gets its own isolated bridge network. Postgres and Redis are not
# exposed externally; only OpenResty has a published port.
resource "docker_network" "app" {
name = "${var.app_name}-network"
driver = "bridge"
}
# ─── Volumes (prod only) ──────────────────────────────────────────────────────
# In dev mode containers are ephemeral; volumes are created only for prod.
resource "docker_volume" "postgres" {
count = local.persistent ? 1 : 0
name = "${var.app_name}-postgres-data"
}
resource "docker_volume" "redis" {
count = local.persistent ? 1 : 0
name = "${var.app_name}-redis-data"
}
# ─── Images ───────────────────────────────────────────────────────────────────
# Standard OpenResty image — used for bind_mount and git_clone modes.
resource "docker_image" "openresty" {
count = var.openresty_source_type != "local_build" ? 1 : 0
name = var.openresty_image
keep_locally = true
}
# Custom-built OpenResty image — used for local_build mode.
# The build context is transferred from the local machine to the remote Docker
# daemon over SSH and built there. Rebuilds are triggered by changes to any
# file in the context directory.
resource "docker_image" "openresty_custom" {
count = var.openresty_source_type == "local_build" ? 1 : 0
name = "${var.app_name}-openresty:latest"
keep_locally = true
build {
context = var.openresty_local_build_context
dockerfile = var.openresty_dockerfile
}
triggers = {
# Rebuild when any file in the build context changes.
context_hash = sha1(join("", [
for f in sort(fileset(var.openresty_local_build_context, "**/*")) :
filesha1("${var.openresty_local_build_context}/${f}")
]))
}
}
resource "docker_image" "postgres" {
name = var.postgres_image
keep_locally = true
}
resource "docker_image" "redis" {
name = var.redis_image
keep_locally = true
}
# ─── PostgreSQL ───────────────────────────────────────────────────────────────
resource "docker_container" "postgres" {
image = docker_image.postgres.image_id
name = "${var.app_name}-postgres"
restart = "unless-stopped"
networks_advanced {
name = docker_network.app.name
}
env = [
"POSTGRES_DB=${var.db_name}",
"POSTGRES_USER=${var.db_user}",
"POSTGRES_PASSWORD=${var.db_password}",
]
dynamic "volumes" {
for_each = local.persistent ? [1] : []
content {
volume_name = docker_volume.postgres[0].name
container_path = "/var/lib/postgresql/data"
}
}
}
# ─── Redis ────────────────────────────────────────────────────────────────────
resource "docker_container" "redis" {
image = docker_image.redis.image_id
name = "${var.app_name}-redis"
restart = "unless-stopped"
networks_advanced {
name = docker_network.app.name
}
dynamic "volumes" {
for_each = local.persistent ? [1] : []
content {
volume_name = docker_volume.redis[0].name
container_path = "/data"
}
}
}
# ─── OpenResty ────────────────────────────────────────────────────────────────
resource "docker_container" "openresty" {
image = (
var.openresty_source_type == "local_build"
? docker_image.openresty_custom[0].image_id
: docker_image.openresty[0].image_id
)
name = "${var.app_name}-openresty"
restart = "unless-stopped"
networks_advanced {
name = docker_network.app.name
}
ports {
internal = 80
external = var.openresty_external_port
}
# bind_mount: mount a pre-existing directory from the remote host.
# The directory must contain a valid nginx.conf and any Lua files.
dynamic "volumes" {
for_each = var.openresty_source_type == "bind_mount" ? [1] : []
content {
host_path = var.openresty_remote_config_path
container_path = "/usr/local/openresty/nginx/conf"
read_only = true
}
}
# git_clone: override the container entrypoint to clone the repo and start openresty.
# The base image must support apk (Alpine). The cloned repo must have an openresty/
# subdirectory with nginx.conf.
entrypoint = var.openresty_source_type == "git_clone" ? local.git_clone_entrypoint : null
# Expose service connection info as env vars so Lua code can use them via os.getenv().
env = [
"APP_NAME=${var.app_name}",
"POSTGRES_HOST=${var.app_name}-postgres",
"POSTGRES_DB=${var.db_name}",
"POSTGRES_USER=${var.db_user}",
"POSTGRES_PASSWORD=${var.db_password}",
"REDIS_HOST=${var.app_name}-redis",
]
depends_on = [
docker_container.postgres,
docker_container.redis,
]
}

View File

@@ -0,0 +1,34 @@
output "openresty_container_name" {
description = "Name of the OpenResty container on the remote host."
value = docker_container.openresty.name
}
output "openresty_external_port" {
description = "External port OpenResty is reachable on."
value = var.openresty_external_port
}
output "postgres_container_name" {
description = "Name of the PostgreSQL container (reachable within the app network)."
value = docker_container.postgres.name
}
output "redis_container_name" {
description = "Name of the Redis container (reachable within the app network)."
value = docker_container.redis.name
}
output "network_name" {
description = "Name of the Docker bridge network shared by all app containers."
value = docker_network.app.name
}
output "postgres_volume_name" {
description = "Name of the PostgreSQL data volume. Empty string in dev (ephemeral) mode."
value = local.persistent ? docker_volume.postgres[0].name : ""
}
output "redis_volume_name" {
description = "Name of the Redis data volume. Empty string in dev (ephemeral) mode."
value = local.persistent ? docker_volume.redis[0].name : ""
}

View File

@@ -0,0 +1,152 @@
# ─── App Identity ─────────────────────────────────────────────────────────────
variable "app_name" {
description = "Unique name for this app deployment. Used as prefix for all container, network and volume names."
type = string
}
variable "environment" {
description = "Deployment environment. Controls volume persistence: 'prod' = named volumes, 'dev' = ephemeral."
type = string
default = "dev"
validation {
condition = contains(["prod", "dev"], var.environment)
error_message = "environment must be 'prod' or 'dev'."
}
}
# ─── OpenResty: source type ────────────────────────────────────────────────────
variable "openresty_source_type" {
description = <<-EOT
How to provide OpenResty config / Lua code. One of:
bind_mount - mount an existing directory from the remote host filesystem
local_build - build a Docker image from a local Dockerfile context (sent to remote daemon)
git_clone - clone a git repo at container startup (requires git-capable base image or apk)
EOT
type = string
validation {
condition = contains(["bind_mount", "local_build", "git_clone"], var.openresty_source_type)
error_message = "openresty_source_type must be 'bind_mount', 'local_build', or 'git_clone'."
}
}
# ─── OpenResty: base image (bind_mount / git_clone) ───────────────────────────
variable "openresty_image" {
description = "OpenResty Docker image used for bind_mount and git_clone modes."
type = string
default = "openresty/openresty:1.25.3-alpine"
}
# ─── OpenResty: bind_mount options ────────────────────────────────────────────
variable "openresty_remote_config_path" {
description = <<-EOT
Absolute path on the REMOTE HOST to mount as /usr/local/openresty/nginx/conf inside the container.
Only used when openresty_source_type = 'bind_mount'.
EOT
type = string
default = ""
}
# ─── OpenResty: local_build options ───────────────────────────────────────────
variable "openresty_local_build_context" {
description = <<-EOT
Path to the local Dockerfile build context directory (relative to the tofu working directory).
The context is transferred to the remote Docker daemon over SSH and built there.
Only used when openresty_source_type = 'local_build'.
EOT
type = string
default = ""
}
variable "openresty_dockerfile" {
description = "Dockerfile filename inside the build context. Only used when openresty_source_type = 'local_build'."
type = string
default = "Dockerfile"
}
# ─── OpenResty: git_clone options ─────────────────────────────────────────────
variable "openresty_git_repo" {
description = <<-EOT
Git repository URL to clone at container startup.
Only used when openresty_source_type = 'git_clone'.
The cloned repo must contain an 'openresty/' directory with a valid nginx.conf.
EOT
type = string
default = ""
}
variable "openresty_git_ref" {
description = <<-EOT
Git ref to checkout. Must be a pinned tag or full commit SHA — never a branch name.
Mutable branch names cause non-reproducible container restarts (the same image
could contain different code each time the container is recreated).
Only used when openresty_source_type = 'git_clone'.
EOT
type = string
default = ""
validation {
condition = var.openresty_git_ref == "" || !contains(
["main", "master", "develop", "dev", "staging", "HEAD", "latest", "trunk"],
var.openresty_git_ref
)
error_message = "openresty_git_ref must be a pinned tag or commit SHA, not a mutable branch name. Got: '${var.openresty_git_ref}'."
}
}
variable "openresty_git_token" {
description = <<-EOT
Optional personal access token for private git repositories.
Injected into the clone URL as oauth2:<token>@.
Only used when openresty_source_type = 'git_clone'.
EOT
type = string
default = ""
sensitive = true
}
# ─── Networking & Ports ───────────────────────────────────────────────────────
variable "openresty_external_port" {
description = "Port exposed on the remote host that forwards to OpenResty port 80."
type = number
}
# ─── PostgreSQL ───────────────────────────────────────────────────────────────
variable "db_name" {
description = "PostgreSQL database name."
type = string
}
variable "db_user" {
description = "PostgreSQL user."
type = string
}
variable "db_password" {
description = "PostgreSQL password."
type = string
sensitive = true
}
variable "postgres_image" {
description = "PostgreSQL Docker image."
type = string
default = "postgres:16-alpine"
}
# ─── Redis ────────────────────────────────────────────────────────────────────
variable "redis_image" {
description = "Redis Docker image."
type = string
default = "redis:7-alpine"
}