Skip to content

GiamPy5/terraform-aws-gatus-ecs

Repository files navigation

terraform-aws-gatus-ecs

Terraform AWS Provider License

Terraform module that deploys Gatus on AWS Fargate with optional Application Load Balancer integration, freshly provisioned ECS infrastructure, and secret-aware configuration delivery via AWS Systems Manager Parameter Store and Secrets Manager.

Table of Contents

Highlights

  • ✅ Zero-to-running Gatus on Fargate with one module invocation.
  • 🧩 Pluggable submodules: toggle ALB, ECS cluster, or service creation independently.
  • 🔐 Secrets-aware config loader resolves __FETCH_FROM_SECRET__.* markers at runtime.
  • 📦 Storage backends for memory, SQLite, or Postgres with minimal toggles.
  • 🛰️ Built-in Service Connect namespace for service discovery inside your VPC.

Architecture

        +---------------------------+
        |  AWS Application Load     |
        |  Balancer (optional)      |
        +-------------+-------------+
                      |
             target_group_arn
                      |
        +-------------v-------------+
        |  AWS ECS Service (Fargate)|  <- gatus container
        |  - gatus-config-loader    |  <- sidecar container
        +-------------+-------------+
                      |
       +--------------+--------------+
       |  AWS ECS Cluster (optional) |
       +--------------+--------------+
                      |
        +-------------v-------------+
        |  AWS SSM Parameter Store  |
        |  AWS Secrets Manager      |
        +---------------------------+

The loader sidecar fetches the latest Gatus configuration (from SSM or a provided ARN), replaces any secret placeholders with data from Secrets Manager, writes the final YAML into /config/user_config.yaml, then signals the main container to boot.

Prerequisites

  • Terraform >= 1.4.0
  • AWS provider ~> 6.0
  • AWS credentials with permissions to create/manage ALB, ECS, IAM, CloudWatch Logs, SSM Parameter Store, Secrets Manager, Service Discovery, and optional KMS keys.

Quick Start

Copy the example below into your Terraform project. Adjust networking, KMS, and database modules to fit your environment.

terraform {
  required_version = ">= 1.4.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 6.0"
    }
  }
}

provider "aws" {
  region = "eu-central-1"
}

module "gatus" {
  source = "github.com/GiamPy5/terraform-aws-gatus-ecs"

  name = "prod-gatus"

  create_alb         = true
  create_ecs_cluster = true
  create_ecs_service = true

  alb = {
    public_subnets      = module.vpc.public_subnets
    vpc_id              = module.vpc.vpc_id
    vpc_cidr_block      = module.vpc.vpc_cidr_block
    acm_certificate_arn = aws_acm_certificate.gatus.arn
  }

  ecs = {
    subnet_ids = module.vpc.private_subnets
  }

  kms_key_arn = aws_kms_key.gatus.arn

  gatus = {
    config = file("${path.module}/config.yaml")
  }

  storage_type        = "postgres"
  postgres_address    = module.rds.address
  postgres_db_name    = "gatus"
  storage_secret_arn  = module.rds.master_user_secret_arn

  tags = {
    Project = "monitoring"
  }
}

See examples/complete for a full environment that provisions networking, RDS, KMS, and VPC endpoints alongside the module.

Configuration Guide

  • Gatus configuration
    Supply the raw YAML via var.gatus.config. Unless you pass gatus.config_ssm_parameter_arn, the module writes the content to SSM Parameter Store and mounts it inside the container at /config/user_config.yaml.

  • Secrets placeholders
    Use placeholders like __FETCH_FROM_SECRET__.storage.password or __FETCH_FROM_SECRET__.oidc.client-secret in the Gatus config. Provide storage_secret_arn and/or oidc_secret_arn; the loader sidecar will fetch the referenced secret and substitute the value at runtime.

    • The ECS task definition injects the secret value into the loader container via native ECS Secrets (STORAGE_SECRET, OIDC_SECRET). If the payload is JSON (e.g., RDS generated credentials), the loader parses it and resolves placeholders without another Secrets Manager API call. When the environment variable still contains an ARN (for backward compatibility), the loader falls back to fetching it directly.
  • Storage selection
    Pick the backend with var.storage_type. Postgres usage requires address, database name, port, and credentials. Supply credentials either as standard variables or, preferably, via Secrets Manager.

  • Security configuration
    Enable basic or oidc auth by setting var.security_type and adding the respective object in var.security_config. OIDC credentials can also be delivered through Secrets Manager placeholders.

  • Postgres credentials
    When storage_type = "postgres" and storage_secret_arn is set, the module automatically injects placeholders for __FETCH_FROM_SECRET__.storage.username and .password, so the loader resolves them from the provided secret.

    • Retrieved usernames/passwords are URL-encoded by the loader to keep the resulting DSN valid even when credentials contain special characters.
  • Existing infrastructure
    When integrating with pre-existing ALB or ECS resources, set create_alb, create_ecs_cluster, or create_ecs_service to false and pass the required ARNs (e.g., var.alb.target_group_arn, var.ecs.cluster_arn).

⚠️ Sensitive data
Prefer submitting credentials through Secrets Manager ARNs (storage_secret_arn, oidc_secret_arn) instead of plaintext Terraform variables. This keeps secrets out of state files, version control, and CLI history.


Requirements

No requirements.

Providers

Name Version
aws n/a

Modules

Name Source Version
alb-integration ./modules/alb-integration n/a
ecs-cluster ./modules/ecs-cluster n/a
ecs-service ./modules/ecs-service n/a

Resources

Name Type
aws_caller_identity.current data source
aws_region.current data source

Inputs

Name Description Type Default Required
additional_config Additional YAML configuration to merge into the rendered Gatus configuration. string "" no
alb Configuration for an existing ALB integration when create_alb is false.
object({
public_subnets = list(string)
backend_port = optional(number, 8080)
acm_certificate_arn = optional(string, "")
vpc_cidr_block = string
vpc_id = string
})
n/a yes
aws_region AWS region override used for providers and logging; defaults to the current region when empty. string "" no
create_alb Controls whether this module provisions the accompanying Application Load Balancer integration. bool true no
create_ecs_cluster Controls whether a new ECS cluster is created instead of using an existing one. bool true no
create_ecs_service Controls whether the ECS service resources are created. bool true no
ecs Inputs for the ECS service, including network configuration and optional cluster ARN.
object({
cluster_arn = optional(string, "")
cpu = optional(number, 2048)
memory = optional(number, 4096)
subnet_ids = list(string)
ingress_security_group_id = optional(string, "")
})
n/a yes
gatus Configuration for the Gatus service, including version and configuration source.
object({
version = optional(string, "v5.29.0")
config_ssm_parameter_arn = optional(string, "")
deployment_trigger_value = optional(string, "")
config = string
})
n/a yes
kms_key_arn ARN of the KMS key used to encrypt the generated Gatus configuration parameter. string "" no
name Base name used when generating resource names. string "terraform-aws-gatus-ecs" no
oidc_secret_arn ARN of the Secrets Manager secret that stores OIDC authentication values. string "" no
postgres_address Hostname or endpoint used to reach the Postgres instance. string "" no
postgres_db_name Database name included in the Postgres connection string. string "" no
postgres_password Password included in the Postgres connection string. string "" no
postgres_port Port number used when building the Postgres connection string. number 5432 no
postgres_secret_arn ARN of a Secrets Manager secret that stores Postgres credentials. string "" no
postgres_username Username included in the Postgres connection string. string "" no
security_config Security configuration values that accompany the selected security_type.
object({
basic = optional(object({
username = string
password_bcrypt_base64 = string
}))
oidc = optional(object({
issuer_url = string
redirect_url = string
client_id = string
client_secret = string
scopes = optional(list(string), ["openid"])
allowed_subjects = optional(list(string), [])
session_ttl = optional(string, "8h")
}))
})
{} no
security_type Authentication mode for the Gatus instance: basic, oidc, or left empty for none. string "" no
sqlite_path Path inside the container for the SQLite database file. string "/data/gatus.db" no
storage_secret_arn ARN of the Secrets Manager secret containing storage credentials for Postgres or other backends. string "" no
storage_type Type of storage backend to configure in the Gatus deployment. string "memory" no
tags Tags applied to all supported resources. map(any) {} no

Outputs

Name Description
load_balancer_dns_name n/a

Operations

  1. Initialise providers: terraform init
  2. Review configuration: terraform plan
  3. Apply changes: terraform apply
  4. Validate drift: rerun terraform plan and review CloudWatch Logs (/aws/ecs/containerinsights/<name>) for loader messages.

Security & IAM

Ensure the task execution role can:

  • Decrypt the configured SSM parameter and any Secrets Manager ARNs you reference.
  • Read logs in CloudWatch Logs (logs:* as configured in modules/ecs-service/main.tf).

The module grants least-privilege statements tailored to the provided inputs, but you remain responsible for KMS key policies and secret creation.

Development

  • Run terraform fmt and terraform validate before submitting changes.
  • Use the examples/complete stack to test real deployments.
  • Lint Python changes in modules/ecs-service/config_loader.py (PEP8) and keep dependencies to standard library only.

Support & Contributions

Issues and contributions are welcome. Please open a ticket or submit a pull request describing your scenario and any validation steps performed (terraform validate, etc.).