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
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:
180
modules/app-openresty-pg-redis/main.tf
Normal file
180
modules/app-openresty-pg-redis/main.tf
Normal 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,
|
||||
]
|
||||
}
|
||||
34
modules/app-openresty-pg-redis/outputs.tf
Normal file
34
modules/app-openresty-pg-redis/outputs.tf
Normal 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 : ""
|
||||
}
|
||||
152
modules/app-openresty-pg-redis/variables.tf
Normal file
152
modules/app-openresty-pg-redis/variables.tf
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user