🤖 chore: move terraform state to S3 with lockfile locking#32
Conversation
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: eeb2249fea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
@codex review Addressed feedback: subnet newbits are now derived dynamically from |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 431232c6a5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
431232c to
49bd807
Compare
|
@codex review Tightened VPC CIDR validation to |
|
Codex Review: Didn't find any major issues. 👍 ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Summary
Move the
terraform/root module from local state to AWS S3 remote state with native lockfile-based locking (no DynamoDB). Bootstrap the S3 bucket, import all 25 existing AWS resources into the new remote state, and align the Terraform config with the deployed infrastructure.Background
The
terraform/directory provisions a sandbox EKS cluster ineu-central-1. It was using the default local backend, meaning state lived whereverterraform applywas last run. This is fragile for team use and risks state loss.Implementation
S3 backend configuration
terraform/versions.tf: Addedbackend "s3"block withuse_lockfile = true,encrypt = true, regioneu-central-1. Bucket/key are supplied at init time via-backend-config(not hardcoded).required_versionfrom>= 1.5to>= 1.11(minimum for S3 lockfile support).S3 bucket bootstrap (out-of-band)
coder-k8s-tfstate-112158171837ineu-central-1with versioning, default AES256 encryption, and all public access blocked.Resource import
import {}blocks to import everything in a singleterraform apply.terraform planis a clean no-op after import.Config drift fixes
terraform/vpc.tf: Replacedcidrsubnets()(which generated/20blocks) withcidrsubnet()per-subnet (generating/24blocks matching deployed infra). CIDRs are still derived fromvar.vpc_cidr.terraform/eks.tf: Addedbootstrap_self_managed_addons = falseto match the deployed cluster.Repo housekeeping
.gitignore: Added standard Terraform ignores (state files,.terraform/, crash logs, plan files, override files,terraform/backend.hcl). Does not ignore.terraform.lock.hcl.flake.nix: Addedterraformto the Nix dev shell, withallowUnfreePredicatescoped to justterraform(BSL 1.1 license).Documentation
terraform/README.md: Updated to reflect S3 remote state, Terraform>= 1.11, added "Remote state backend" section with bucket bootstrap, IAM, auth, and migration docs. Updated Usage section with-backend-configworkflow.Validation
terraform plan→ "No changes. Infrastructure matches the configuration."make build✅make test✅make lint✅nix-instantiate --parse flake.nix✅terraform fmt -check✅Risks
cidrsubnet()change produces identical CIDRs to deployed infra, confirmed by clean plan. Ifvar.vpc_cidrwere ever changed, subnet CIDRs would shift — but that's a VPC rebuild regardless.terraformonly.📋 Implementation Plan
Plan: Move
terraform/state to AWS S3 (S3 lockfile, no DynamoDB)Context / Why
We have a Terraform root module under
terraform/that provisions a sandbox EKS cluster ineu-central-1. Today it uses the default local backend (state lives on whoever ranterraform apply).Goal:
eu-central-1).use_lockfile), no DynamoDB.Evidence (what I checked)
terraform/versions.tf: no backend configured;required_version = ">= 1.5".terraform/*.tf: root module resources are all plainaws_*resources (no external modules), so imports are addressable directly (e.g.,aws_vpc.this,aws_eks_cluster.this)..gitignore: currently no Terraform ignores.flake.nix: dev shell includesawscli2but does not includeterraform.use_lockfile = true.<key>.tflockin the same bucket/prefix.Implementation Plan
1) Repo changes (backend config, docs, housekeeping, Nix)
1.1. Update Terraform version + S3 backend config
File:
terraform/versions.tfrequired_version = ">= 1.11"(lockfile is no longer described as experimental; DynamoDB locking is deprecated).There are two workable patterns; pick one:
Option A (recommended): keep bucket/key out of git; commit only the backend type + safety settings
s3use_lockfile = trueencrypt = trueregion = "eu-central-1"bucket+keyvia-backend-configat init time (see §2).Option B: commit the full backend config (bucket + key) into the repo
use_lockfile = trueandencrypt = true.1.2. Document remote state + locking + bootstrap steps
File:
terraform/README.mdUpdate:
>= 1.11.terraform initcommand to supply bucket/key.TF_VAR_aws_profile, also setAWS_PROFILE(or setprofile = ...in the backend config file) soterraform initcan access the S3 bucket.<key>.tflock; whys3:DeleteObjectis needed for the lock object.1.3. Housekeeping: ignore local Terraform artifacts (but commit dependency lock file)
File:
.gitignoreAdd standard Terraform ignores:
*.tfstate*.tfstate.*.terraform/crash.logcrash.*.log*.tfplan*.tfplan.*override.tf,override.tf.json,*_override.tf,*_override.tf.json.terraformrc,terraform.rcterraform init -backend-config=...; do not commit):terraform/backend.hclImportant nuance:
.terraform.lock.hcl(provider dependency lock). Best practice is to commit it.Suggested block:
1.4. Add Terraform CLI to the Nix flake dev shell
File:
flake.nixAdd
terraformtodevShells.default.packagesnearawscli2:Follow-up (verification step):
nix developand confirmterraform versionis>= 1.11. If nixpkgs’terraformis older for any reason, switch to a versioned package (e.g.,terraform_1/terraform_1_11depending on nixpkgs naming).2) Bootstrap the S3 backend bucket (eu-central-1)
Terraform can’t create its own backend bucket (chicken-and-egg), so create it out-of-band.
2.1 Choose names
coder-k8s-tfstate-<account-id>orcoder-k8s-tfstate-<org>-<env>.terraform-ncp3/sandbox-eks/terraform.tfstate.2.2 Create + harden the bucket
Example AWS CLI (adjust bucket name):
Optional but recommended hardening:
aws:SecureTransport) and encryption headers.2.3 IAM permissions for Terraform backend access
Whoever runs Terraform needs:
s3:GetObject,s3:PutObject.<key>.tflock:s3:GetObject,s3:PutObject,s3:DeleteObject.s3:ListBucket.Add/update an IAM policy (illustrative; scope down to your bucket + prefix):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::<bucket>", "Condition": { "StringLike": { "s3:prefix": [ "terraform-ncp3/sandbox-eks/*" ] } } }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject"], "Resource": "arn:aws:s3:::<bucket>/terraform-ncp3/sandbox-eks/terraform.tfstate" }, { "Effect": "Allow", "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::<bucket>/terraform-ncp3/sandbox-eks/terraform.tfstate.tflock" } ] }3) Initialize the backend (and migrate state if it exists)
Directory:
terraform/3.1 Create a backend config file (recommended for Option A)
Create
terraform/backend.hcl(do not commit):Then initialize (from within
terraform/):After the first successful init:
terraform/.terraform.lock.hclexists and commit it (it pins provider selections for reproducible runs).3.2 If an existing local state file exists, migrate it instead of importing
If someone already has the correct local state file from previous applies, the safest path is to migrate it:
This preserves all resource addresses and avoids manual
terraform importfor most cases.4) Final step: discover existing resources and import into the new remote state
This section applies if:
.tfstateis missing / not trustworthy, or4.1 Guardrails before importing
terraform applyuntil imports are complete andterraform planlooks sane.aws_regionshould beeu-central-1cluster_namedefault issandbox-eks${cluster_name}-nodes(defaultsandbox-eks-nodes)countand depend on the first two AZ names returned by AWS. Import indices must match the AZ suffix in the subnetNametag.4.2 Discover IDs in AWS (examples)
Use tags from the config (notably
Name = "${var.cluster_name}-...") to locate resource IDs:🔎 Example AWS CLI discovery commands (eu-central-1)
4.3 Import commands (addresses + ID formats)
Import order is mostly for human sanity; Terraform doesn’t always require strict ordering, but this tends to minimize confusing errors.
📥 Import checklist for this repo’s current resources
Networking (
terraform/vpc.tf)IAM (
terraform/iam.tf)EKS (
terraform/eks.tf)4.4 Verify state matches reality
After imports:
terraform planshould ideally be a no-op.terraform apply.Common import-related issues to watch for:
5) Validation / acceptance criteria
terraform initsucceeds with the S3 backend anduse_lockfile = true..tflockobject appears briefly during a plan/apply..gitignoreprevents state and.terraform/from being committed (includingterraform/backend.hcl).terraform/.terraform.lock.hclis committed.nix developprovidesterraformand its version is>= 1.11.terraform plandoes not propose recreating the existing cluster/VPC (only expected drift).Generated with
mux• Model:anthropic:claude-opus-4-6• Thinking:xhigh• Cost:$4.83