CI/CD integration
For platform engineers adding compliance.tf modules to a pipeline. When you finish, the pipeline's terraform init authenticates to the compliance.tf registry and downloads the modules; plan and apply stay as they are. This page covers what every platform shares. The platform pages below cover the rest.
Evaluating rather than setting up? Rollback is covered in Compatibility with terraform-aws-modules, and what happens if the registry is unreachable in Make the case.
- Compliance.tf is a private Terraform registry. Any CI/CD system that can run the Terraform CLI can use compliance.tf modules.
- The only setup required is providing a registry token so
terraform initcan authenticate. - Your Terraform commands (
init,plan,apply) stay exactly the same. Only the token configuration is new.
Before you start
- A compliance.tf account with an active subscription: start a free trial or sign in.
- An organization-owned registry token for the pipeline, one per consumer: see Create a token.
- A Terraform or OpenTofu version your modules support: see Terraform and OpenTofu versions.
- New to compliance.tf? Complete Get started first.
When you run terraform login soc2.compliance.tf on your machine, the CLI stores a short-lived token that expires after 1 day. That works for local development but not for a pipeline. A pipeline is something a team depends on, so give it an organization-owned token: it carries the organization's plan on every host and keeps working when the person who created it leaves. A personal token from the Access Tokens page breaks or narrows to one person's plan when its owner leaves; Registry tokens explains the difference. Store the token as a secret in your CI/CD platform and pass it to Terraform as an environment variable.
How authentication works
Which credential Terraform reads depends on the form of your module source.
Registry form (source = "soc2.compliance.tf/...", with a version argument). Terraform reads a TF_TOKEN_* environment variable:
TF_TOKEN_soc2_compliance_tf=ctf_YOUR_TOKEN_HEREThe variable name is the registry hostname with dots replaced by underscores and a TF_TOKEN_ prefix:
| Registry hostname | Environment variable |
|---|---|
soc2.compliance.tf | TF_TOKEN_soc2_compliance_tf |
hipaa.compliance.tf | TF_TOKEN_hipaa_compliance_tf |
pcidss.compliance.tf | TF_TOKEN_pcidss_compliance_tf |
bobthecorp.compliance.tf (your organization's host) | TF_TOKEN_bobthecorp_compliance_tf |
If you use several hosts in the same configuration, set one TF_TOKEN_* variable per hostname. The same token value works for all of them; only the variable name changes. Every platform page below uses this form. Reference: Terraform CLI TF_TOKEN_* documentation.
HTTPS form (source = "https://soc2.compliance.tf/...?version=..."). On a framework host it is the form that takes ?disable= and ?add_rules=; on your organization's host, the form that takes ?ruleset=. Terraform fetches it with its HTTPS getter, which reads .netrc and ignores TF_TOKEN_*. Multi-environment setup below shows the pipeline step; the .netrc format is on Registry endpoints, and what each host accepts is on Rulesets in the console.
Choose your platform
Every platform uses the same pattern: store your compliance.tf token as a secret, then expose it as TF_TOKEN_soc2_compliance_tf when Terraform runs.
Token setup, complete workflow example, and optional Checkov and ctfkit checks
Protected CI/CD variables and pipeline YAML configuration
Workspace environment variables and variable sets for the HCP Terraform runner
Spacelift, Atlantis, env0, Jenkins, CircleCI, Azure DevOps, and generic CI environments
Multi-environment setup
Teams often need different compliance postures for different environments. Production might enforce all SOC 2 controls, while development relaxes controls that slow iteration (like S3 Object Lock, which AWS never turns off once a bucket has it, so a dev bucket would carry it for good). The example below uses the HTTPS form, so the pipeline needs a .netrc entry as well as, or instead of, TF_TOKEN_*.
Same framework, different controls per environment
Use disable= in the HTTPS module source to relax specific controls in non-production environments. Each environment root keeps its own literal source line: Terraform does not accept a variable or a local in a module source, as One source per environment explains. (The examples below assume a logging_bucket module elsewhere in the configuration; substitute your own logging bucket reference.)
module "s3_bucket" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0"
bucket = "my-app-data-prod"
logging = {
target_bucket = module.logging_bucket.s3_bucket_id
target_prefix = "s3-access-logs/my-app-prod/"
}
# Plus the KMS key, replication and lifecycle rule SOC 2 also needs
}module "s3_bucket" {
source = "https://soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws?version=5.0.0&disable=s3_bucket_object_lock_enabled"
bucket = "my-app-data-dev"
logging = {
target_bucket = module.logging_bucket.s3_bucket_id
target_prefix = "s3-access-logs/my-app-dev/"
}
# Plus the KMS key, replication and lifecycle rule SOC 2 also needs
}Each environment directory has its own Terraform configuration, and your CI/CD pipeline runs against the directory for the target environment. The full set of SOC 2 inputs for the S3 bucket module is in the S3 bucket migration playbook.
Token setup for the HTTPS form
Write a .netrc file for the job, point NETRC at it, and delete it when the job ends. Run this in the step that runs terraform init, with the token injected from your platform's secret store as CTF_TOKEN:
# A per-job file, so a shared build agent keeps no credentials
export NETRC="$(mktemp)"
trap 'rm -f "$NETRC"' EXIT
# One machine entry per compliance.tf host in your module sources
printf 'machine soc2.compliance.tf\n login ci\n password %s\n' "${CTF_TOKEN}" > "$NETRC"
terraform init
terraform plan -out=tfplanlogin can be any string; the token goes in password. If the same configuration also uses registry-form sources, keep the TF_TOKEN_* variable as well.
Token setup for several framework hosts
If your environments use different framework hosts, set a TF_TOKEN_* variable for each registry-form hostname, and add a machine line for each HTTPS-form hostname:
env:
TF_TOKEN_soc2_compliance_tf: ${{ secrets.CTF_TOKEN }}
TF_TOKEN_hipaa_compliance_tf: ${{ secrets.CTF_TOKEN }}A single token value works across all compliance.tf hosts. To customize controls per environment, see Turn controls on and off.
Audit artifacts from CI
Your pipeline already produces evidence an auditor can use. Save the plan output and scan reports as artifacts, and each run leaves a record behind.
Save plan output as JSON
Terraform plan output in JSON format shows which resources get created and what values are set. This includes the settings the compliance.tf modules enforce. It is a record that those settings were in the plan at deployment time.
- name: Save Plan JSON
run: terraform show -json tfplan > plan.json
- name: Upload Plan Artifacts
uses: actions/upload-artifact@v4
with:
name: terraform-plan-${{ github.sha }}
path: plan.json
retention-days: 365Set retention-days to match the evidence retention you agree with your assessor. Common anchors: PCI DSS requires 12 months of audit-log history and HIPAA six years for required documentation; which artifacts count as evidence, and for how long, varies by audit. Note the platform ceilings: GitHub Actions caps artifact retention at 90 days for public repos and 400 days for private repos. For longer horizons, have the pipeline copy artifacts to long-term storage you control, such as an S3 bucket with a lifecycle policy.
Save scan reports
If you run Checkov or another scanner, save its output alongside the plan:
- name: Upload Scan Report
uses: actions/upload-artifact@v4
with:
name: checkov-report-${{ github.sha }}
path: checkov-report.json
retention-days: 365See Audit evidence to build these artifacts into an evidence package.
Token management for teams
- One organization-owned token per consumer for CI/CD: one per pipeline or runner, named after it, created on the organization's tokens screen. Revoking one then stops one consumer. See Create a token.
- Personal tokens for local development. Each engineer generates their own via
terraform loginor the Access Tokens page. - Rotation. Registry tokens are valid until revoked. Rotate them on a regular cadence (quarterly recommended): create a new token, update your CI secrets, then revoke the old token.
- Revocation. If a token is compromised, revoke it immediately where it was created (the organization's tokens screen, or the Access Tokens page for a personal token), generate a new one, and update every pipeline that used the revoked token.
Troubleshooting
Error accessing remote module registry or Failed to retrieve available versions for module
Module download from the compliance.tf registry failed: the token is not set, or the hostname in the variable name does not match your module source. Check that:
- The
TF_TOKEN_*variable is available in the step that runsterraform init. - The hostname in the variable name matches your module source exactly (
soc2.compliance.tfbecomesTF_TOKEN_soc2_compliance_tf).
401 Unauthorized
The token is invalid or expired.
- Registry tokens do not expire unless revoked. Check that the token has not been revoked.
- A token from
terraform loginexpires after 1 day. Use a long-lived CI token instead. - Check that the full token value was copied (tokens start with
ctf_).
403 Forbidden
The token is valid but your subscription does not include the requested framework.
- Check your subscription at app.compliance.tf to confirm the framework host is included.
- If you use a framework host such as
hipaa.compliance.tf, confirm your subscription covers HIPAA.
Variable name conversion mistakes
The most common mistake is getting the TF_TOKEN_* variable name wrong. The conversion rule:
- Start with the registry hostname:
soc2.compliance.tf - Replace every dot with an underscore:
soc2_compliance_tf - Add the
TF_TOKEN_prefix:TF_TOKEN_soc2_compliance_tf
Check that you have not used dashes, uppercase letters, or extra characters.
.netrc not working
.netrc is used with the HTTPS form, not the registry form. If your module source looks like source = "soc2.compliance.tf/..." (no https:// prefix), you need TF_TOKEN_* or .terraformrc credentials, not .netrc.
If your source uses source = "https://soc2.compliance.tf/...", check that your .netrc file:
- Is in your home directory, or at the path in the
NETRCenvironment variable - Has a
machinevalue matching the hostname - Uses
passwordfor the token value (logincan be any string)
Next steps
- GitHub Actions - the complete workflow, if your pipelines run on GitHub
- Turn controls on and off - relax or add controls per module
- Registry endpoints - the registry and HTTPS forms side by side
- Audit evidence - turn pipeline artifacts into an evidence package