Learn/Advanced Topics

JSON in CI/CD — GitHub Actions, Terraform & Docker

JSON drives modern infrastructure: Terraform state, GitHub Actions matrices, Docker labels, AWS CloudFormation templates, and every API call in your pipeline scripts. This guide covers how to generate, parse, validate, and pass JSON through CI/CD workflows.

Where JSON Appears in DevOps

JSON Across the CI/CD Lifecycle
StageJSON UsageExamples
SourceConfig filespackage.json, tsconfig.json, .eslintrc.json
CI PipelineMatrix strategies, outputsGitHub Actions fromJson(), step outputs
BuildBuild manifests, lockfilespackage-lock.json, build-manifest.json
InfrastructureInfrastructure as CodeTerraform .tf.json, CloudFormation
DeploymentContainer configsDocker labels, Kubernetes ConfigMaps
MonitoringStructured loggingJSON log lines, error reports

1. GitHub Actions & JSON

Dynamic Matrix from JSON

.github/workflows/test.ymlyaml
1name: Test Matrix
2on: push
3
4jobs:
5 setup:
6 runs-on: ubuntu-latest
7 outputs:
8 matrix: ${{ steps.set-matrix.outputs.matrix }}
9 steps:
10 - uses: actions/checkout@v4
11 - id: set-matrix
12 run: |
13 MATRIX=$(cat .github/test-matrix.json)
14 echo "matrix=$MATRIX" >> "$GITHUB_OUTPUT"
15
16 test:
17 needs: setup
18 strategy:
19 matrix: ${{ fromJson(needs.setup.outputs.matrix) }}
20 runs-on: ${{ matrix.os }}
21 steps:
22 - uses: actions/checkout@v4
23 - uses: actions/setup-node@v4
24 with:
25 node-version: ${{ matrix.node }}
26 - run: npm ci && npm test
.github/test-matrix.jsonjson
1{
2 "os": ["ubuntu-latest", "windows-latest", "macos-latest"],
3 "node": ["18", "20", "22"],
4 "include": [
5 { "os": "ubuntu-latest", "node": "22", "coverage": true }
6 ],
7 "exclude": [
8 { "os": "windows-latest", "node": "18" }
9 ]
10}

Passing JSON Between Steps

Step outputs with JSONyaml
1steps:
2 - name: Get deployment info
3 id: deploy-info
4 run: |
5 INFO=$(curl -s https://api.example.com/deployments/latest)
6 echo "info=$(echo $INFO | jq -c .)" >> "$GITHUB_OUTPUT"
7
8 - name: Use deployment info
9 run: |
10 VERSION=$(echo '${{ steps.deploy-info.outputs.info }}' | jq -r '.version')
11 echo "Deploying version: $VERSION"

Validating JSON in CI

JSON validation stepyaml
1- name: Validate JSON config files
2 run: |
3 for file in config/*.json; do
4 echo "Validating $file..."
5 python -m json.tool "$file" > /dev/null || exit 1
6 done
7 echo "All JSON files are valid"

2. Terraform & JSON

Terraform supports JSON as an alternative to HCL. Files ending in .tf.json are parsed as JSON:

main.tf.json — Terraform in JSON syntaxjson
1{
2 "terraform": {
3 "required_providers": {
4 "aws": {
5 "source": "hashicorp/aws",
6 "version": "~> 5.0"
7 }
8 }
9 },
10 "provider": {
11 "aws": {
12 "region": "us-east-1"
13 }
14 },
15 "resource": {
16 "aws_s3_bucket": {
17 "data_bucket": {
18 "bucket": "my-app-data-2026",
19 "tags": {
20 "Environment": "production",
21 "ManagedBy": "terraform"
22 }
23 }
24 }
25 }
26}

Terraform State (terraform.tfstate)

Terraform stores infrastructure state as JSON. Understanding this format helps with debugging and importing:

terraform.tfstate (partial)json
1{
2 "version": 4,
3 "terraform_version": "1.9.0",
4 "resources": [
5 {
6 "mode": "managed",
7 "type": "aws_s3_bucket",
8 "name": "data_bucket",
9 "instances": [
10 {
11 "attributes": {
12 "id": "my-app-data-2026",
13 "bucket": "my-app-data-2026",
14 "region": "us-east-1",
15 "arn": "arn:aws:s3:::my-app-data-2026"
16 }
17 }
18 ]
19 }
20 ]
21}

Warning

Never edit terraform.tfstate manually. It contains sensitive data (resource IDs, connection strings) and must stay consistent with real infrastructure. Use terraform state commands instead.

Using jq with Terraform

Query Terraform outputsbash
1# Get Terraform outputs as JSON
2terraform output -json | jq '.api_url.value'
3
4# List all resource IDs in state
5terraform show -json | jq '.values.root_module.resources[].values.id'
6
7# Extract specific resource attributes
8terraform show -json | jq '.values.root_module.resources[] | select(.type == "aws_s3_bucket") | .values.bucket'

3. Docker & JSON

Docker Inspect Output

Inspecting containers with jqbash
1# Get container IP address
2docker inspect my-container | jq '.[0].NetworkSettings.IPAddress'
3
4# Get all environment variables
5docker inspect my-container | jq '.[0].Config.Env'
6
7# Get port mappings
8docker inspect my-container | jq '.[0].NetworkSettings.Ports'
9
10# Get image labels
11docker inspect my-image | jq '.[0].Config.Labels'

Container Labels as JSON Metadata

Dockerfile labelstext
1LABEL maintainer="[email protected]"
2LABEL version="2.1.0"
3LABEL description="API service"
4LABEL org.opencontainers.image.source="https://github.com/org/repo"
5LABEL deploy.config='{"replicas": 3, "memory": "512Mi", "cpu": "250m"}'

4. AWS CloudFormation (JSON Templates)

CloudFormation template (partial)json
1{
2 "AWSTemplateFormatVersion": "2010-09-09",
3 "Description": "API Lambda function",
4 "Parameters": {
5 "Environment": {
6 "Type": "String",
7 "AllowedValues": ["dev", "staging", "production"],
8 "Default": "dev"
9 }
10 },
11 "Resources": {
12 "ApiFunction": {
13 "Type": "AWS::Lambda::Function",
14 "Properties": {
15 "FunctionName": { "Fn::Sub": "api-${Environment}" },
16 "Runtime": "nodejs20.x",
17 "Handler": "index.handler",
18 "MemorySize": 256,
19 "Timeout": 30,
20 "Environment": {
21 "Variables": {
22 "NODE_ENV": { "Ref": "Environment" }
23 }
24 }
25 }
26 }
27 }
28}

5. Structured JSON Logging

Production applications should emit structured JSON logs for machine parsing:

Structured log line (JSON Lines format)json
1{"timestamp":"2026-04-02T14:30:00Z","level":"info","message":"Request handled","method":"GET","path":"/api/users","status":200,"duration_ms":45,"request_id":"req_abc123"}
2{"timestamp":"2026-04-02T14:30:01Z","level":"error","message":"Database query failed","error":"connection timeout","query":"SELECT * FROM users","duration_ms":5000,"request_id":"req_def456"}
Analyzing JSON logs with jqbash
1# Find all errors
2cat app.log | jq 'select(.level == "error")'
3
4# Slow requests (>1 second)
5cat app.log | jq 'select(.duration_ms > 1000) | {path, duration_ms, timestamp}'
6
7# Count requests by status code
8cat app.log | jq -s 'group_by(.status) | map({status: .[0].status, count: length})'
9
10# Average response time
11cat app.log | jq -s '[.[].duration_ms] | add / length'

Best Practices

  • Validate all JSON config files in CI before deployment
  • Use jq -c (compact) when passing JSON in environment variables or step outputs
  • Store dynamic configs as JSON files in the repo, not hardcoded in pipeline YAML
  • Use structured JSON logging in production for queryable, machine-parseable logs
  • Pin Terraform state to remote backends, never commit tfstate to git
  • Don't store secrets in JSON config files — use environment variables or secret managers
  • Don't embed unescaped JSON in shell strings — use files or base64 encoding

Try It — Validate a CI/CD JSON Config

Try It Yourself

A GitHub Actions matrix config — validate before using

Frequently Asked Questions

Where does JSON appear in CI/CD pipelines?
JSON is everywhere in CI/CD: GitHub Actions matrix strategies, Terraform state files and variable definitions, Docker container labels, AWS CloudFormation templates, Azure ARM templates, npm/yarn lockfiles, build tool outputs, and API calls within pipeline scripts.
Should I use JSON or YAML for GitHub Actions?
GitHub Actions workflow files must be YAML (.yml). However, JSON appears inside workflows: matrix strategies, output parsing with fromJson(), and API calls with jq. You can also define reusable JSON configs that workflows read at runtime.
What is Terraform JSON syntax?
Terraform supports both HCL (its native syntax) and JSON for configuration files. JSON Terraform files use .tf.json extension and follow a specific structure where block types become JSON object keys. JSON is useful for generated configs and API-driven infrastructure management.
How do I pass JSON between CI/CD steps?
In GitHub Actions, use outputs with toJson() and fromJson(). For shell-based pipelines, write JSON to a file and read it in the next step. Use jq to extract specific values. For complex data, base64-encode the JSON to avoid shell escaping issues.
How do I validate JSON in a CI/CD pipeline?
Add a validation step: use python -m json.tool, jq ., or npx jsonlint-cli to validate JSON files. For schema validation, use ajv-cli. Fail the pipeline if validation fails to prevent broken configs from reaching production.