Skip to content

Commit 5e66666

Browse files
committed
feat(terraform): add Terraform backend module for remote state management
Add a new module for creating a secure Terraform backend infrastructure using AWS S3 and DynamoDB. The module includes: - S3 bucket for state storage with versioning and encryption - DynamoDB table for state locking - Comprehensive documentation with setup instructions - Security best practices implementation
1 parent 4654c60 commit 5e66666

File tree

5 files changed

+273
-0
lines changed

5 files changed

+273
-0
lines changed

terraform-backend/README.md

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
# Terraform Backend
2+
3+
![Terraform](https://img.shields.io/badge/terraform-%235835CC.svg?style=for-the-badge&logo=terraform&logoColor=white)
4+
5+
Remote state backend infrastructure for Terraform using AWS S3 and DynamoDB. This module creates the necessary infrastructure to store Terraform state remotely and securely, including state locking to prevent concurrent modifications.
6+
7+
> [!IMPORTANT]
8+
> This is a foundational component that should be created before other infrastructure resources. This module must be run with local state initially, as it's creating its own backend.
9+
10+
## Stack
11+
12+
- S3 Bucket (state storage)
13+
- DynamoDB Table (state locking)
14+
- Amazon Certificate Manager
15+
16+
### S3 Bucket
17+
- **Name**: `${service_name}-${stage}-terraform-state`
18+
- **Versioning**: Enabled to maintain state history
19+
- **Encryption**: AES256 by default
20+
- **Public Access**: Completely blocked
21+
- **Deletion Protection**: Enabled via `prevent_destroy`
22+
23+
### DynamoDB Table
24+
- **Name**: `${service_name}-${stage}-terraform-state-lock`
25+
- **Billing Mode**: Pay per request
26+
- **Hash Key**: `LockID`
27+
- **Purpose**: State locking concurrency control
28+
29+
## Pricing
30+
31+
In most cases, the price will be near to zero for small projects.
32+
33+
The pricing is based on:
34+
- S3: Storage ($0.023 per GB) and requests
35+
- DynamoDB: Pay-per-request pricing ($1.25 per million writes, $0.25 per million reads)
36+
37+
See [AWS S3 Pricing](https://aws.amazon.com/s3/pricing/) and [AWS DynamoDB Pricing](https://aws.amazon.com/dynamodb/pricing/) for more details.
38+
39+
> [!NOTE]
40+
> For production workloads, monitor your usage. Versioning enabled on S3 will increase storage costs over time.
41+
42+
## Requirements
43+
44+
- AWS CLI configured
45+
- Terraform >= 1.0
46+
- AWS credentials with permissions for:
47+
- S3 (CreateBucket, PutBucketPolicy, PutBucketVersioning, etc.)
48+
- DynamoDB (CreateTable, DescribeTable, etc.)
49+
50+
## Supported variables
51+
52+
| Name | Type | Default | Description |
53+
| -------------- | -------- | ----------- | ------------------------------------------ |
54+
| `region` | `string` | `us-east-1` | AWS region where resources will be created |
55+
| `service_name` | `string` | `cluster` | Service name (used in resource naming) |
56+
| `stage` | `string` | `dev` | Environment (dev, test, prod, etc.) |
57+
58+
## Usage
59+
60+
### 1. Configure variables
61+
62+
Copy the example file and configure your variables:
63+
64+
```bash
65+
cp terraform.tfvars.example terraform.tfvars
66+
```
67+
68+
Edit the `terraform.tfvars` file:
69+
70+
```hcl
71+
service_name = "your-service-name"
72+
stage = "prod"
73+
```
74+
75+
### 2. Deploy the backend infrastructure
76+
77+
```bash
78+
# Initialize Terraform
79+
terraform init
80+
81+
# Review the plan
82+
terraform plan
83+
84+
# Apply changes
85+
terraform apply
86+
```
87+
88+
### 3. Configure remote backend in other modules
89+
90+
After creating the infrastructure, configure other modules to use this backend:
91+
92+
```hcl
93+
terraform {
94+
backend "s3" {
95+
bucket = "your-service-name-prod-terraform-state"
96+
key = "path/to/your/terraform.tfstate"
97+
region = "us-east-1"
98+
dynamodb_table = "your-service-name-prod-terraform-state-lock"
99+
encrypt = true
100+
}
101+
}
102+
```
103+
104+
### 4. Migrate existing state (if needed)
105+
106+
If you already have local state and want to migrate to this backend:
107+
108+
```bash
109+
# 1. Create the backend infrastructure (this module)
110+
terraform apply
111+
112+
# 2. Add backend configuration to other modules
113+
# 3. Run migration
114+
terraform init -migrate-state
115+
```
116+
117+
## Outputs
118+
119+
| Name | Description |
120+
| --------------------- | -------------------------------------------- |
121+
| `s3_bucket_name` | Name of the S3 bucket |
122+
| `s3_bucket_arn` | ARN of the S3 bucket |
123+
| `s3_bucket_region` | Region of the S3 bucket |
124+
| `dynamodb_table_name` | Name of the DynamoDB table for state locking |
125+
| `dynamodb_table_arn` | ARN of the DynamoDB table |
126+
127+
## Security
128+
129+
This module implements the following security best practices:
130+
131+
-**Encryption**: State encrypted at rest (AES256)
132+
-**Versioning**: State version history
133+
-**Public Access Blocked**: No public access to the bucket
134+
-**State Locking**: Prevents concurrent modifications
135+
-**Deletion Protection**: Bucket protected against accidental deletion
136+
-**Tagging**: Default tags applied for governance
137+
138+
## Troubleshooting
139+
140+
### Permission Errors
141+
If you receive permission errors, verify that your AWS credentials have the necessary permissions listed in the requirements.
142+
143+
### Name Conflict
144+
If the S3 bucket already exists, change the `service_name` or `stage` to generate a unique name.
145+
146+
### Corrupted State
147+
S3 versioning allows you to recover previous versions of the state in case of issues.
148+
149+
### Destroying the Infrastructure
150+
151+
The S3 bucket has deletion protection enabled. To completely destroy the infrastructure:
152+
153+
1. Remove the `prevent_destroy` lifecycle rule from the S3 bucket in `main.tf`
154+
2. Run `terraform apply` to update the configuration
155+
3. Run `terraform destroy`

terraform-backend/inputs.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
variable "region" {
2+
default = "us-east-1"
3+
description = "AWS region where the resources will be created"
4+
type = string
5+
}
6+
7+
variable "service_name" {
8+
default = "cluster"
9+
description = "Service name"
10+
type = string
11+
nullable = false
12+
}
13+
14+
variable "stage" {
15+
default = "dev"
16+
description = "Stage (e.g. dev, test, prod)"
17+
type = string
18+
}

terraform-backend/main.tf

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
provider "aws" {
2+
region = var.region
3+
4+
default_tags {
5+
tags = {
6+
Service = local.service_full_name # Will be: ${service_name}-${stage}
7+
ServiceName = var.service_name
8+
Stage = var.stage
9+
}
10+
}
11+
}
12+
13+
locals {
14+
service_full_name = "${var.service_name}-${var.stage}"
15+
}
16+
17+
resource "aws_s3_bucket" "bucket" {
18+
bucket = "${local.service_full_name}-terraform-state"
19+
20+
# Prevent accidental deletion of the bucket
21+
lifecycle {
22+
prevent_destroy = true
23+
}
24+
25+
tags = {
26+
Name = "${local.service_full_name}-terraform-state"
27+
}
28+
}
29+
30+
# Enable versioning to maintain state history
31+
resource "aws_s3_bucket_versioning" "terraform_state" {
32+
bucket = aws_s3_bucket.bucket.id
33+
34+
versioning_configuration {
35+
status = "Enabled"
36+
}
37+
}
38+
39+
# Enable default encryption
40+
resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state" {
41+
bucket = aws_s3_bucket.bucket.id
42+
43+
rule {
44+
apply_server_side_encryption_by_default {
45+
sse_algorithm = "AES256"
46+
}
47+
}
48+
}
49+
50+
# Block public access to the bucket
51+
resource "aws_s3_bucket_public_access_block" "terraform_state" {
52+
bucket = aws_s3_bucket.bucket.id
53+
54+
block_public_acls = true
55+
block_public_policy = true
56+
ignore_public_acls = true
57+
restrict_public_buckets = true
58+
}
59+
60+
# DynamoDB table for state locking
61+
resource "aws_dynamodb_table" "dynamo" {
62+
name = "${local.service_full_name}-terraform-state-lock"
63+
billing_mode = "PAY_PER_REQUEST"
64+
hash_key = "LockID"
65+
66+
attribute {
67+
name = "LockID"
68+
type = "S"
69+
}
70+
71+
tags = {
72+
Name = "${local.service_full_name}-terraform-locks"
73+
}
74+
}

terraform-backend/outputs.tf

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
output "s3_bucket_name" {
2+
description = "Name of the S3 bucket"
3+
value = aws_s3_bucket.bucket.id
4+
}
5+
6+
output "s3_bucket_arn" {
7+
description = "ARN of the S3 bucket"
8+
value = aws_s3_bucket.bucket.arn
9+
}
10+
11+
output "s3_bucket_region" {
12+
description = "Region of the S3 bucket"
13+
value = aws_s3_bucket.bucket.region
14+
}
15+
16+
output "dynamodb_table_name" {
17+
description = "Name of the DynamoDB table for state locking"
18+
value = aws_dynamodb_table.dynamo.name
19+
}
20+
21+
output "dynamodb_table_arn" {
22+
description = "ARN of the DynamoDB table for state locking"
23+
value = aws_dynamodb_table.dynamo.arn
24+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
service_name = "terraform-backend"
2+
stage = "prod"

0 commit comments

Comments
 (0)