Terraform Module: Scheduled Lambda + EventBridge

Live

A Terraform module for the ops-automation shape I kept rebuilding by hand: a Lambda function on an EventBridge schedule — the pattern behind alert digests, cleanup tasks, and report generation. Five resources, always the same, and always with the same two corners cut when assembled quickly: an over-broad execution role and a log group Lambda auto-creates with infinite retention. The module packages those five resources once, with the defaults done correctly.

Least-privilege by construction

Instead of attaching the AWS-managed AWSLambdaBasicExecutionRole — which grants log actions across the account — the module builds its own policy document granting exactly two actions, logs:CreateLogStream and logs:PutLogEvents, scoped to "${aws_cloudwatch_log_group.this.arn}:*": this function's log group and its streams, nothing else. The log group itself (/aws/lambda/${var.function_name}) is created explicitly with a retention_in_days setting (default 30), and the function declares depends_on against it, so retention is enforced from the first invocation rather than after Lambda has already auto-created a never-expiring group. When a job needs more than logging — say, reading one SSM parameter — callers pass extra_policy_arns and attach scoped policies to the role, rather than the module widening its own grants.

Scheduling and packaging

Scheduling is a single schedule_expression input that accepts either form EventBridge supports — rate(1 hour) or cron(0 6 * * ? *) — wired as a rule, a target pointing at the function ARN, and an aws_lambda_permission whose source_arn is pinned to that specific rule, so only this schedule can invoke the function. A schedule_enabled boolean flips the rule between ENABLED and DISABLED, letting you pause a job without destroying it.

The module deliberately does not build the Lambda package. It takes package_path to a pre-built zip and derives source_code_hash via filebase64sha256() so redeploys trigger only when the artifact actually changes — build tooling stays in CI, not in Terraform. Four inputs are required (function_name, package_path, handler, schedule_expression); the rest default sensibly: python3.12 runtime, 60s timeout, 128 MB memory, empty environment map, and a tags map applied to every resource. Outputs expose the function ARN, role ARN, schedule rule ARN, and log group name for downstream wiring.

Example and pipeline

The examples/basic directory is a working nightly-cleanup job: cron(0 6 * * ? *), a 120-second timeout, 14-day log retention, and a LOG_LEVEL environment variable — a realistic override of the defaults, not a toy. CI runs on every push and pull request: terraform fmt -check, terraform validate against a backendless init, and tflint with the AWS ruleset plugin (pinned to 0.38.0). Releases are wired to release-please using the terraform-module release type — conventional commits on main will drive semantic version bumps and changelog entries as the module matures, so consumers will be able to pin it by tag. Requires Terraform >= 1.5 and AWS provider >= 5.0.