Skip to content
Casey Labs

Application delivery depends on infrastructure: GitLab configuration, Terraform workspaces, Kubernetes clusters, runner namespaces, policy sets, cloud permissions, and network paths. If that infrastructure changes without review, the delivery platform can become unsafe even when application pipelines look healthy.

HCP Terraform is the provisioning and infrastructure governance plane in this guide. It owns remote state, locking, speculative plans, policy checks, and approved applies. It does not own GitLab pipeline logic, per-repository CI content, or runtime enforcement inside application pipelines.

Infrastructure-as-code means writing desired infrastructure state in files instead of configuring everything manually in a UI. A Terraform file can describe a GitLab project, a cloud network, a Kubernetes namespace, or an HCP Terraform workspace. Terraform compares the desired state to real state and produces a plan.

The plan is the key governance artifact. It answers: what will change if we apply this?

That makes review possible. A platform engineer can inspect a plan before it changes branch protections, creates runner infrastructure, modifies workspace variables, or updates a policy set. Without a plan, the organization is trusting hidden automation.

HCP Terraform is useful because it separates plan generation, policy evaluation, state management, and apply permissions from an individual laptop or CI job. The platform can require speculative plans for review, centralize sensitive variables, attach policy sets, and keep a record of infrastructure changes.

That boundary keeps Terraform focused on structural state:

  • GitLab groups, projects, and protected refs
  • HCP Terraform projects, workspaces, and policy sets
  • EKS runner infrastructure and Helm releases
  • baseline configuration that should be reviewed before apply

It also keeps repository creation from becoming a manual setup exercise. Terraform can create the GitLab group structure, place new projects in the right groups, and apply baseline repository settings at creation time.

GitLab can trigger workflows and hold review history, but HCP Terraform should own the plan and apply lifecycle for infrastructure with durable blast radius.

The project uses workspace layers by blast radius:

Layer Example workspace Change frequency
Organization structure gitlab-org-structure Low
Project provisioning gitlab-projects-platform Medium
Policy baseline gitlab-policy-baseline Controlled
Runner infrastructure eks-runners-standard Medium

This avoids both extremes. A workspace per repository creates state sprawl. A single global workspace creates a dangerous blast radius. Layering by domain keeps plans reviewable and limits the scope of each apply.

A practical split is:

  • one workspace for organization structure and top-level groups
  • one workspace for baseline GitLab policy and configuration objects
  • one workspace per top-level group or platform domain when the workspace manages multiple repositories
  • separate workspaces for runner infrastructure and external integrations such as IAM, networking, and deployment services

The governance stack delegates HCP Terraform object creation to a module:

module "platform_governance" {
source = "../../modules/tfc-workspace"
organization = var.organization
project_name = "SDLC Platform"
workspaces = var.workspaces
policy_sets = var.policy_sets
}

The module boundary matters because workspace policy is not incidental. It is part of the platform design.

New projects should start with the baseline already applied:

  • group placement
  • protected default branches
  • merge request rules
  • initial approval rules
  • required security policies
  • default CI/CD component includes
  • repository variables that are safe to set at creation time

GitLab-native templates and CI/CD components still own developer workflow shape. Terraform should bootstrap the durable settings and integrations that define the platform baseline.

The credential rule is conservative: avoid long-lived credentials wherever the platform has a better option.

For AWS, HCP Terraform dynamic provider credentials are the preferred model. For GitLab, the provider still needs API access, so tokens should be scoped, sensitive, and stored as HCP Terraform workspace variables rather than committed. Kubernetes and Helm access should come from approved runner infrastructure workflows with short-lived cluster access.

Credentials do not disappear. The job is to keep them narrow, auditable, and separate from source code.

Terraform plans should be evaluated before apply. The reference OPA policy rejects dangerous or noncompliant changes, including public GitLab groups, unprotected GitLab project variables, unmasked secret-like environment variables, and destructive changes without an explicit exception:

deny contains msg if {
resource := input.resource_changes[_]
resource.type == "gitlab_group"
visibility := object.get(resource.change.after, "visibility_level", "private")
visibility == "public"
msg := sprintf("GitLab group must not be public: %s", [resource.address])
}
deny contains msg if {
resource := input.resource_changes[_]
resource.type == "gitlab_project_variable"
after := resource.change.after
object.get(after, "protected", false) == false
msg := sprintf("GitLab project variable must be protected: %s", [resource.address])
}

These rules are intentionally small. They prove the shape of the governance model without pretending every enterprise exception is known on day one.

The operating model is:

  1. Commit desired state changes.
  2. Generate a speculative plan.
  3. Evaluate policy checks.
  4. Require platform approval for structural changes.
  5. Apply only from approved HCP Terraform runs.

This is the infrastructure equivalent of protected branches in GitLab. Safe changes should feel normal, and risky changes should be visible.

Next, the guide moves from durable infrastructure state to the runtime environment that executes CI jobs.