Start creating new homelab settings with OpenTofu
This commit is contained in:
+10
@@ -0,0 +1,10 @@
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.*
|
||||
crash.log
|
||||
*.tfvars
|
||||
override.tf
|
||||
override.tf.json
|
||||
.terraformrc
|
||||
terraform.rc
|
||||
.env
|
||||
@@ -0,0 +1,23 @@
|
||||
module "system_globals" {
|
||||
source = "./modules/00-globals/system"
|
||||
}
|
||||
|
||||
// Application services
|
||||
module "services" {
|
||||
source = "./services"
|
||||
}
|
||||
|
||||
module "caddy" {
|
||||
source = "./modules/01-networking/caddy-service"
|
||||
volume_path = "./docker/infrastructure/"
|
||||
domains = [
|
||||
"blackchaosnl.duckdns.org",
|
||||
"blackchaosnl.myaddr.io",
|
||||
"blackchaosnl.myaddr.dev",
|
||||
"blackchaosnl.myaddr.tools"
|
||||
]
|
||||
tls_email = "your-email@example.com" # For Let's Encrypt
|
||||
container_name = "caddy"
|
||||
service_definitions = module.services.service_definitions
|
||||
networks = ["default"]
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "dotenv" "system_config" {}
|
||||
|
||||
// Outputs
|
||||
output "timezone" {
|
||||
description = "System timezone"
|
||||
value = data.dotenv.system_config.entries.TIMEZONE
|
||||
}
|
||||
|
||||
output "volume_host" {
|
||||
description = "Base directory for host volumes"
|
||||
value = data.dotenv.system_config.entries.VOLUME_HOST
|
||||
}
|
||||
|
||||
output "puid" {
|
||||
description = "PUID for Docker containers"
|
||||
value = data.dotenv.system_config.entries.PUID
|
||||
}
|
||||
|
||||
output "pgid" {
|
||||
description = "PGID for Docker containers"
|
||||
value = data.dotenv.system_config.entries.PGID
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "dotenv" "system_config" {}
|
||||
|
||||
// Outputs
|
||||
output "tls_email" {
|
||||
description = "TLS email"
|
||||
value = data.dotenv.system_config.entries.TLS_EMAIL
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
# Caddy Proxy Module
|
||||
|
||||
This module creates a Caddy reverse proxy server that dynamically configures itself based on service definitions passed to it.
|
||||
|
||||
## Overview
|
||||
|
||||
The Caddy Proxy module:
|
||||
- Accepts service definitions that specify whether to expose them via reverse proxy
|
||||
- Dynamically generates Caddyfile configuration from these service definitions
|
||||
- Supports custom Caddy configuration blocks per service
|
||||
- Deploys a Caddy container with the generated configuration
|
||||
- Manages TLS certificates automatically using Let's Encrypt
|
||||
- Creates DNS records for services with configurable Cloudflare proxying settings
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Integration
|
||||
|
||||
Add the module to your main Terraform configuration:
|
||||
|
||||
```hcl
|
||||
module "homelab_caddy_proxy" {
|
||||
source = "./modules/01-networking/caddy-proxy"
|
||||
domains = ["yourdomain.com"]
|
||||
tls_email = "your-email@example.com" # For Let's Encrypt
|
||||
container_name = "caddy-proxy"
|
||||
service_definitions = module.services.service_definitions
|
||||
networks = ["your-docker-network"]
|
||||
}
|
||||
```
|
||||
|
||||
### Service Definition Format
|
||||
|
||||
Services should include the following fields to be properly exposed through Caddy:
|
||||
|
||||
```hcl
|
||||
{
|
||||
name = "service-name"
|
||||
endpoint = "service-container:port"
|
||||
subdomains = ["app", "dashboard"] # Will create app.yourdomain.com, dashboard.yourdomain.com
|
||||
|
||||
# Option 1: Simplified Caddy configuration via options
|
||||
caddy_options = {
|
||||
"health_path" = "/health"
|
||||
"health_interval" = "30s"
|
||||
"header_up X-Real-IP" = "{http.request.remote}"
|
||||
# Additional reverse_proxy options as needed
|
||||
}
|
||||
|
||||
# Option 2: Full custom Caddy configuration (takes precedence if both are provided)
|
||||
caddy_config = <<-EOT
|
||||
# Raw Caddy configuration goes here
|
||||
reverse_proxy /api/* api-backend:8080
|
||||
reverse_proxy /* frontend:3000
|
||||
header X-Powered-By "My Awesome Homelab"
|
||||
log {
|
||||
output file /var/log/access.log
|
||||
}
|
||||
EOT
|
||||
}
|
||||
```
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description | Type | Default |
|
||||
|----------|-------------|------|---------|
|
||||
| `container_name` | The name of the Caddy container | `string` | `""` (generates "caddy-proxy") |
|
||||
| `image_tag` | The tag of the Caddy Docker image to use | `string` | `"latest"` |
|
||||
| `domains` | The domain names to use for services | `list(string)` | - |
|
||||
| `tls_email` | Email address for Let's Encrypt | `string` | - |
|
||||
| `service_definitions` | List of service definitions to evaluate | `list(object)` | - |
|
||||
| `networks` | List of Docker networks to connect to | `list(string)` | `[]` |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Output | Description |
|
||||
|--------|-------------|
|
||||
| `container_name` | The name of the deployed Caddy container |
|
||||
| `config_hash` | The SHA256 hash of the generated Caddyfile content |
|
||||
| `service_sites` | Map of generated Caddy site configurations |
|
||||
|
||||
## Example Service Integration
|
||||
|
||||
### Basic Service with Default Settings
|
||||
|
||||
```hcl
|
||||
# Example based on ntfy (reverse-proxy only with direct IP exposure)
|
||||
output "service_definition" {
|
||||
description = "Service definition for a notification service"
|
||||
value = {
|
||||
name = "ntfy"
|
||||
primary_port = 80
|
||||
endpoint = "http://ntfy:80"
|
||||
subdomains = ["ntfy"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Service with Custom Caddy Configuration
|
||||
|
||||
```hcl
|
||||
# Example showing a service with custom Caddy configuration
|
||||
output "service_definition" {
|
||||
description = "Service definition with custom Caddy configuration"
|
||||
value = {
|
||||
name = "custom-service"
|
||||
primary_port = 8080
|
||||
endpoint = "http://custom-service:8080"
|
||||
subdomains = ["custom"]
|
||||
caddy_config = <<-EOT
|
||||
# Handle API requests specially
|
||||
handle /api/* {
|
||||
reverse_proxy custom-service:8080 {
|
||||
header_up X-Real-IP {remote}
|
||||
}
|
||||
}
|
||||
|
||||
# Handle all other requests
|
||||
handle {
|
||||
reverse_proxy custom-service:8080
|
||||
header +Access-Control-Allow-Origin "*"
|
||||
}
|
||||
EOT
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,141 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = var.container_name != "" ? var.container_name : "caddy"
|
||||
image_tag = var.image_tag != "" ? var.image_tag : "latest"
|
||||
|
||||
// Filter services to only include those that should be published via reverse proxy
|
||||
proxy_services = [
|
||||
for service in var.service_definitions :
|
||||
service if length(service.subdomains) > 0
|
||||
]
|
||||
|
||||
// Transform service definitions into Caddyfile blocks
|
||||
caddy_site_configs = flatten([
|
||||
for service in local.proxy_services :
|
||||
[
|
||||
for domain in var.domains : [
|
||||
for subdomain in service.subdomains : {
|
||||
site_address = "${subdomain}.${domain}"
|
||||
endpoint = service.endpoint
|
||||
service_name = service.name
|
||||
tls_email = var.tls_email
|
||||
has_custom_config = service.caddy_config != ""
|
||||
custom_config = service.caddy_config
|
||||
reverse_proxy_options = service.caddy_options
|
||||
}
|
||||
]
|
||||
]
|
||||
])
|
||||
|
||||
caddyfile_default = <<-EOT
|
||||
{
|
||||
email ${var.tls_email}
|
||||
|
||||
log {
|
||||
format console
|
||||
output stdout
|
||||
}
|
||||
}
|
||||
|
||||
(headers) {
|
||||
header {
|
||||
-server
|
||||
-via
|
||||
|
||||
Permissions-Policy interest-cohort=()
|
||||
|
||||
Strict-Transport-Security "max-age=31536000; includesSubDomains; preload"
|
||||
|
||||
X-Content-Type-Options "nosniff"
|
||||
|
||||
X-Frame-Options DENY
|
||||
}
|
||||
}
|
||||
|
||||
EOT
|
||||
|
||||
// Generate the main Caddyfile content
|
||||
caddyfile_content = merge(local.caddyfile_default, join("\n\n", [
|
||||
for site in local.caddy_site_configs :
|
||||
site.has_custom_config ?
|
||||
// Use the custom Caddy config if provided
|
||||
<<-EOT
|
||||
${site.site_address} {
|
||||
import headers
|
||||
${site.custom_config}
|
||||
}
|
||||
EOT
|
||||
:
|
||||
// Otherwise use the standard reverse proxy config with options
|
||||
<<-EOT
|
||||
${site.site_address} {
|
||||
import headers
|
||||
reverse_proxy ${site.endpoint} {
|
||||
${join("\n ", [
|
||||
for key, value in site.reverse_proxy_options :
|
||||
"${key} ${value}"
|
||||
])}
|
||||
}
|
||||
}
|
||||
EOT
|
||||
]))
|
||||
}
|
||||
|
||||
resource "docker_volume" "caddy_config" {
|
||||
name = "${local.container_name}_config"
|
||||
}
|
||||
|
||||
// Create Caddyfile in the volume path
|
||||
resource "local_file" "caddyfile" {
|
||||
content = local.caddyfile_content
|
||||
filename = "${var.volume_path}/caddy/Caddyfile"
|
||||
}
|
||||
|
||||
|
||||
module "caddy" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
|
||||
container_name = local.container_name
|
||||
image = "caddy"
|
||||
tag = local.image_tag
|
||||
|
||||
volumes = [
|
||||
{
|
||||
host_path = "${var.volume_path}/${image}/data"
|
||||
container_path = "/data"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = "${var.volume_path}/${image}/config"
|
||||
container_path = "/config"
|
||||
read_only = false
|
||||
},
|
||||
{
|
||||
host_path = "${var.volume_path}/${image}/Caddyfile"
|
||||
container_path = "/etc/caddy/Caddyfile"
|
||||
read_only = true
|
||||
}
|
||||
]
|
||||
|
||||
ports = [
|
||||
{
|
||||
external = "80"
|
||||
internal = "80"
|
||||
protocol = "tcp"
|
||||
},
|
||||
{
|
||||
external = "443"
|
||||
internal = "443"
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
|
||||
networks = var.networks
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
output "container_name" {
|
||||
description = "The name of the deployed Caddy container"
|
||||
value = module.caddy.container_name
|
||||
}
|
||||
|
||||
output "config_hash" {
|
||||
description = "The SHA256 hash of the generated Caddyfile content"
|
||||
value = sha256(local.caddyfile_content)
|
||||
}
|
||||
|
||||
output "service_sites" {
|
||||
description = "Map of generated Caddy site configurations"
|
||||
value = {
|
||||
for site in local.caddy_site_configs : site.site_address => site.endpoint
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
variable "container_name" {
|
||||
description = "The name of the Caddy container"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "The tag of the Caddy Docker image to use"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "domains" {
|
||||
description = "Which domain name to use for services"
|
||||
type = list(object({
|
||||
name = string
|
||||
}))
|
||||
}
|
||||
|
||||
variable "tls_email" {
|
||||
description = "Email address to use for TLS certificate generation with Let's Encrypt"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "service_definitions" {
|
||||
description = "List of service definitions to evaluate for exposure through Caddy"
|
||||
type = list(object({
|
||||
name = string
|
||||
endpoint = string
|
||||
subdomains = optional(list(string), [])
|
||||
publish_via = optional(string)
|
||||
caddy_config = optional(string, "")
|
||||
caddy_options = optional(map(string), {})
|
||||
}))
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of Docker networks to connect the Caddy container to"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_network" "this" {
|
||||
name = var.name
|
||||
driver = var.driver
|
||||
internal = var.internal
|
||||
attachable = var.attachable
|
||||
ipam_driver = var.ipam_driver
|
||||
|
||||
dynamic "ipam_config" {
|
||||
for_each = var.subnet != "" ? [1] : []
|
||||
content {
|
||||
subnet = var.subnet
|
||||
gateway = var.gateway
|
||||
ip_range = var.ip_range
|
||||
aux_address = var.aux_address
|
||||
}
|
||||
}
|
||||
|
||||
options = var.options
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
output "network_id" {
|
||||
description = "The ID of the Docker network"
|
||||
value = docker_network.this.id
|
||||
}
|
||||
|
||||
output "name" {
|
||||
description = "The name of the Docker network"
|
||||
value = docker_network.this.name
|
||||
}
|
||||
|
||||
output "network_driver" {
|
||||
description = "The driver of the Docker network"
|
||||
value = docker_network.this.driver
|
||||
}
|
||||
|
||||
output "ipam_config" {
|
||||
description = "The IPAM configuration of the Docker network"
|
||||
value = docker_network.this.ipam_config
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
variable "name" {
|
||||
description = "Name of the Docker network"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "driver" {
|
||||
description = "Name of the network driver to use"
|
||||
type = string
|
||||
default = "bridge"
|
||||
}
|
||||
|
||||
variable "internal" {
|
||||
description = "Restrict external access to the network if true"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "attachable" {
|
||||
description = "Enable manual container attachment if true"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "ipam_driver" {
|
||||
description = "Driver used for IP address management"
|
||||
type = string
|
||||
default = "default"
|
||||
}
|
||||
|
||||
variable "subnet" {
|
||||
description = "Subnet in CIDR format that represents a network segment"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "gateway" {
|
||||
description = "IPv4 or IPv6 gateway for the subnet"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "ip_range" {
|
||||
description = "Range of IPs from which to allocate container IPs"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "aux_address" {
|
||||
description = "Auxiliary IPv4 or IPv6 addresses used by the driver"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "labels" {
|
||||
description = "Labels to add to the network"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "options" {
|
||||
description = "Network driver specific options"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# Generic Docker Service Module
|
||||
|
||||
This is a reusable OpenTofu module for deploying Docker containers with configurable options. It serves as the foundation for specific application modules in this homelab project.
|
||||
|
||||
## Features
|
||||
|
||||
- Pull and manage Docker images
|
||||
- Configure container networking, ports, and volumes
|
||||
- Set environment variables and labels
|
||||
- Configure resource limits and constraints
|
||||
- Set up health checks
|
||||
- Support for container logging options
|
||||
|
||||
## Usage
|
||||
|
||||
This module is typically called by application-specific modules rather than used directly, but can be used as follows:
|
||||
|
||||
```hcl
|
||||
module "my_service" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
|
||||
container_name = "my-service"
|
||||
image = "organization/image"
|
||||
tag = "latest"
|
||||
|
||||
restart_policy = "unless-stopped"
|
||||
network_mode = "bridge"
|
||||
|
||||
// Port mappings
|
||||
ports = [
|
||||
{
|
||||
internal = 8080
|
||||
external = 8080
|
||||
protocol = "tcp"
|
||||
}
|
||||
]
|
||||
|
||||
// Volume mappings
|
||||
volumes = [
|
||||
{
|
||||
host_path = "/path/on/host"
|
||||
container_path = "/path/in/container"
|
||||
read_only = false
|
||||
}
|
||||
]
|
||||
|
||||
// Environment variables
|
||||
env_vars = {
|
||||
VARIABLE_NAME = "value"
|
||||
}
|
||||
|
||||
// Container labels
|
||||
labels = {
|
||||
"com.example.description" = "My service description"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Required Providers
|
||||
|
||||
This module requires the Docker provider to be configured in your root module:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
required_providers {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
See the `variables.tf` file for a complete list of input variables and their descriptions.
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
| --------------- | ------------------------------------------- |
|
||||
| container_name | Name of the Docker container |
|
||||
| container_id | ID of the Docker container |
|
||||
| image_id | ID of the Docker image |
|
||||
| ip_address | IP address of the container (if applicable) |
|
||||
| container_ports | Published ports of the container |
|
||||
@@ -0,0 +1,150 @@
|
||||
// Generic Docker service module
|
||||
// Creates and manages a Docker container with configurable options
|
||||
module "system_globals" {
|
||||
source = "../../00-globals/system"
|
||||
}
|
||||
|
||||
terraform {
|
||||
required_providers {
|
||||
docker = {
|
||||
source = "kreuzwerker/docker"
|
||||
}
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
locals {
|
||||
network_mode = var.network_mode
|
||||
container_name = var.container_name
|
||||
image_name = "${var.image}:${var.tag}"
|
||||
|
||||
default_env_vars = {
|
||||
TZ = module.system_globals.timezone
|
||||
PUID = var.puid != null ? var.puid : module.system_globals.puid
|
||||
PGID = var.pgid != null ? var.pgid : module.system_globals.pgid
|
||||
}
|
||||
|
||||
env_vars = merge(var.env_vars, local.default_env_vars)
|
||||
|
||||
// Prepare ports configuration
|
||||
ports_config = [
|
||||
for port in var.ports : {
|
||||
internal = port.internal
|
||||
external = port.external
|
||||
protocol = port.protocol
|
||||
}
|
||||
]
|
||||
|
||||
// Prepare volumes configuration
|
||||
volumes_config = [
|
||||
for volume in var.volumes : {
|
||||
host_path = volume.host_path
|
||||
container_path = volume.container_path
|
||||
read_only = volume.read_only
|
||||
}
|
||||
]
|
||||
|
||||
// Merge provided labels with monitoring labels
|
||||
merged_labels = merge(var.labels)
|
||||
}
|
||||
|
||||
// Pull the Docker image
|
||||
resource "docker_image" "service_image" {
|
||||
name = local.image_name
|
||||
keep_locally = var.keep_image_locally
|
||||
pull_triggers = [var.tag]
|
||||
}
|
||||
|
||||
// Create the Docker container
|
||||
resource "docker_container" "service_container" {
|
||||
name = local.container_name
|
||||
image = docker_image.service_image.image_id
|
||||
|
||||
restart = var.restart_policy
|
||||
|
||||
# Set the network mode (bridge, host, etc.)
|
||||
network_mode = local.network_mode
|
||||
|
||||
# Add host mappings (entries for /etc/hosts)
|
||||
dynamic "host" {
|
||||
for_each = var.host_mappings
|
||||
content {
|
||||
host = host.value.host
|
||||
ip = host.value.ip
|
||||
}
|
||||
}
|
||||
|
||||
# Dynamically configure ports based on the provided list
|
||||
dynamic "ports" {
|
||||
for_each = local.ports_config
|
||||
content {
|
||||
internal = ports.value.internal
|
||||
external = ports.value.external
|
||||
protocol = ports.value.protocol
|
||||
}
|
||||
}
|
||||
|
||||
# Dynamically configure networks based on the provided list
|
||||
dynamic "networks_advanced" {
|
||||
for_each = var.networks
|
||||
content {
|
||||
name = networks_advanced.value
|
||||
}
|
||||
}
|
||||
|
||||
# Dynamically configure volumes based on the provided list
|
||||
dynamic "volumes" {
|
||||
for_each = local.volumes_config
|
||||
content {
|
||||
host_path = volumes.value.host_path
|
||||
container_path = volumes.value.container_path
|
||||
read_only = volumes.value.read_only
|
||||
}
|
||||
}
|
||||
|
||||
# Configure environment variables - map to array of strings
|
||||
env = [for k, v in local.env_vars : "${k}=${v}"]
|
||||
|
||||
# Set container labels
|
||||
dynamic "labels" {
|
||||
for_each = local.merged_labels
|
||||
content {
|
||||
label = labels.key
|
||||
value = labels.value
|
||||
}
|
||||
}
|
||||
|
||||
# Add container healthcheck if configured
|
||||
dynamic "healthcheck" {
|
||||
for_each = var.healthcheck != null ? [var.healthcheck] : []
|
||||
content {
|
||||
test = healthcheck.value.test
|
||||
interval = healthcheck.value.interval
|
||||
timeout = healthcheck.value.timeout
|
||||
start_period = healthcheck.value.start_period
|
||||
retries = healthcheck.value.retries
|
||||
}
|
||||
}
|
||||
|
||||
# Set resource limits if specified
|
||||
memory = var.memory_limit
|
||||
memory_swap = var.memory_swap_limit
|
||||
cpu_shares = var.cpu_shares
|
||||
|
||||
# Other container options
|
||||
dns = var.dns
|
||||
dns_search = var.dns_search
|
||||
hostname = var.hostname
|
||||
domainname = var.domainname
|
||||
user = var.user
|
||||
working_dir = var.working_dir
|
||||
command = var.command
|
||||
entrypoint = var.entrypoint
|
||||
privileged = var.privileged
|
||||
|
||||
# Set log options
|
||||
log_driver = var.log_driver
|
||||
log_opts = var.log_opts
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
output "container_name" {
|
||||
description = "Name of the Docker container"
|
||||
value = docker_container.service_container.name
|
||||
}
|
||||
|
||||
output "container_id" {
|
||||
description = "ID of the Docker container"
|
||||
value = docker_container.service_container.id
|
||||
}
|
||||
|
||||
output "image_id" {
|
||||
description = "ID of the Docker image"
|
||||
value = docker_image.service_image.id
|
||||
}
|
||||
|
||||
output "ip_address" {
|
||||
description = "IP address of the container (if applicable)"
|
||||
value = docker_container.service_container.network_data != null ? docker_container.service_container.network_data[0].ip_address : null
|
||||
}
|
||||
|
||||
output "container_ports" {
|
||||
description = "Published ports of the container"
|
||||
value = docker_container.service_container.ports
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
variable "container_name" {
|
||||
description = "Name of the Docker container"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "image" {
|
||||
description = "Docker image name"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "tag" {
|
||||
description = "Docker image tag"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "keep_image_locally" {
|
||||
description = "Whether to keep the Docker image locally after pulling"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "restart_policy" {
|
||||
description = "Docker restart policy (no, always, unless-stopped, on-failure)"
|
||||
type = string
|
||||
default = "always"
|
||||
}
|
||||
|
||||
variable "network_mode" {
|
||||
description = "Docker network mode (bridge, host, etc.)"
|
||||
type = string
|
||||
default = "bridge"
|
||||
}
|
||||
|
||||
variable "ports" {
|
||||
description = "List of port mappings"
|
||||
type = list(object({
|
||||
internal = number
|
||||
external = number
|
||||
protocol = string
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to connect the container to"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "volumes" {
|
||||
description = "List of volume mappings"
|
||||
type = list(object({
|
||||
host_path = string
|
||||
container_path = string
|
||||
read_only = bool
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "env_vars" {
|
||||
description = "Environment variables for the container"
|
||||
type = map(string)
|
||||
default = {}
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "puid" {
|
||||
description = "User ID for the container"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "pgid" {
|
||||
description = "Group ID for the container"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "labels" {
|
||||
description = "Docker container labels"
|
||||
type = map(string)
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "managed_by_caddy" {
|
||||
description = "Enable mounting container through labels"
|
||||
type = bool
|
||||
default = true
|
||||
}
|
||||
|
||||
variable "host_mappings" {
|
||||
description = "Additional host mappings for the container (/etc/hosts entries)"
|
||||
type = list(object({
|
||||
host = string
|
||||
ip = string
|
||||
}))
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "healthcheck" {
|
||||
description = "Container healthcheck configuration"
|
||||
type = object({
|
||||
test = list(string)
|
||||
interval = string
|
||||
timeout = string
|
||||
start_period = optional(string)
|
||||
retries = number
|
||||
})
|
||||
default = null
|
||||
}
|
||||
|
||||
// Resource limits
|
||||
variable "memory_limit" {
|
||||
description = "Memory limit for the container (in MB)"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "memory_swap_limit" {
|
||||
description = "Memory swap limit for the container (in MB)"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "cpu_shares" {
|
||||
description = "CPU shares for the container (relative weight)"
|
||||
type = number
|
||||
default = null
|
||||
}
|
||||
|
||||
// Networking options
|
||||
variable "dns" {
|
||||
description = "DNS servers for the container"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "dns_search" {
|
||||
description = "DNS search domains for the container"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "hostname" {
|
||||
description = "Container hostname"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "domainname" {
|
||||
description = "Container domainname"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
// Execution options
|
||||
variable "user" {
|
||||
description = "User to run commands as inside the container"
|
||||
type = string
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "working_dir" {
|
||||
description = "Working directory inside the container"
|
||||
type = string
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "command" {
|
||||
description = "Command to run when starting the container"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "entrypoint" {
|
||||
description = "Entrypoint for the container"
|
||||
type = list(string)
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "privileged" {
|
||||
description = "Run container in privileged mode"
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
// Logging options
|
||||
variable "log_driver" {
|
||||
description = "Log driver for the container"
|
||||
type = string
|
||||
default = "json-file"
|
||||
}
|
||||
|
||||
variable "log_opts" {
|
||||
description = "Log driver options"
|
||||
type = map(string)
|
||||
default = {
|
||||
max-size = "10m"
|
||||
max-file = "3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
variable "image_tag" {
|
||||
description = "The tag for the JellyFin container image. Default: Latest"
|
||||
type = string
|
||||
default = "latest"
|
||||
}
|
||||
|
||||
variable "volume_path" {
|
||||
description = "Base directory for volumes"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "networks" {
|
||||
description = "List of networks to which the container should be attached"
|
||||
type = list(string)
|
||||
default = []
|
||||
}
|
||||
|
||||
variable "user_id" {
|
||||
description = "User ID for container permissions"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
variable "group_id" {
|
||||
description = "Group ID for container permissions"
|
||||
type = string
|
||||
default = "1000"
|
||||
}
|
||||
|
||||
variable "timezone" {
|
||||
description = "Timezone for the container"
|
||||
type = string
|
||||
default = "Europe/Helsinki"
|
||||
}
|
||||
|
||||
locals {
|
||||
container_name = "jellyfin"
|
||||
jellyfin_image = "docker.io/jellyfin/jellyfin"
|
||||
jellyfin_tag = var.image_tag
|
||||
env_file = "${path.module}/.env"
|
||||
jellyfin_internal_port = 8096
|
||||
|
||||
jellyfin_volumes = [
|
||||
{
|
||||
host_path = "/mnt/storage/media"
|
||||
container_path = "/media"
|
||||
read_only = true
|
||||
},
|
||||
{
|
||||
host_path = "${volume_path}/${container_name}/config"
|
||||
container_path = "/config"
|
||||
},{
|
||||
host_path = "${volume_path}/${container_name}/cache"
|
||||
container_path = "/cache"
|
||||
},
|
||||
]
|
||||
|
||||
jellyfin_env_vars = {
|
||||
PUID = var.user_id
|
||||
PGID = var.group_id
|
||||
TZ = var.timezone
|
||||
}
|
||||
}
|
||||
|
||||
module "jellyfin" {
|
||||
source = "../../10-services-generic/docker-service"
|
||||
container_name = local.container_name
|
||||
image = local.jellyfin_image
|
||||
tag = local.jellyfin_tag
|
||||
volumes = local.jellyfin_volumes
|
||||
env_vars = local.jellyfin_env_vars
|
||||
networks = concat(var.networks)
|
||||
restart_policy = "always"
|
||||
}
|
||||
|
||||
output "service_definition" {
|
||||
description = "General service definition with optional ingress configuration"
|
||||
value = {
|
||||
name = local.container_name
|
||||
primary_port = local.jellyfin_internal_port
|
||||
endpoint = "http://${local.container_name}:${local.jellyfin_internal_port}"
|
||||
subdomains = ["tv"]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
podman = {
|
||||
source = "kreuzwerker/docker"
|
||||
version = "~> 3.6.0"
|
||||
}
|
||||
random = {
|
||||
source = "hashicorp/random"
|
||||
version = "~> 3.5.1"
|
||||
}
|
||||
dotenv = {
|
||||
source = "germanbrew/dotenv"
|
||||
version = "1.2.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
locals {
|
||||
module_dir = "../modules"
|
||||
root_volume = module.system_globals.volume_host
|
||||
volume_host = "${module.system_globals.volume_host}/appconfig"
|
||||
}
|
||||
|
||||
module "system_globals" {
|
||||
source = "${local.module_dir}/00-globals/system"
|
||||
}
|
||||
|
||||
module "homelab_docker_network" {
|
||||
source = "${local.module_dir}/01-networking/docker-network"
|
||||
name = "default"
|
||||
driver = "bridge"
|
||||
attachable = true
|
||||
subnet = "10.88.0.0/16"
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
output "service_definitions" {
|
||||
description = "Service definitions for all services"
|
||||
value = [
|
||||
module.actualbudget.service_definition,
|
||||
module.affine.service_definition,
|
||||
module.calibre.service_definition,
|
||||
module.copyparty.service_definition,
|
||||
module.crawl4ai.service_definition,
|
||||
module.emulatorjs.service_definition,
|
||||
module.glance.service_definition,
|
||||
module.linkwarden.service_definition,
|
||||
module.n8n.service_definition,
|
||||
module.n8n.n8n_mcp_service_definition,
|
||||
module.nocodb.service_definition,
|
||||
module.ntfy.service_definition,
|
||||
module.portainer.service_definition,
|
||||
module.pterodactyl_wings.service_definition,
|
||||
module.pterodactyl_panel.service_definition,
|
||||
module.searxng.service_definition
|
||||
]
|
||||
}
|
||||
|
||||
output "homelab_docker_network_name" {
|
||||
description = "The name of the Docker network"
|
||||
value = module.homelab_docker_network.name
|
||||
}
|
||||
Reference in New Issue
Block a user