Skip to content

CI/CD from Zero — Government Edition

This guide explains how to build a continuous integration and continuous delivery pipeline for a government web service, from the first automated test run through production deployment with the controls and evidence that government programs require.

Engineers and technical leads at government agencies who are deploying manually, have no pipeline at all, or are starting fresh on a new service will find this guide most useful.


TL;DR

  • CI/CD means: merge code frequently, test automatically, and automate the path to production.
  • Manual deployments are risky, slow, and leave no audit trail — all of which matter more in government.
  • A minimum viable pipeline runs lint, unit tests, an accessibility scan, and a security scan before anything reaches staging.
  • Government pipelines need separation of duties: the person who writes the code cannot be the only one who approves its production deploy.
  • Evidence capture is not optional — your pipeline must prove what code was deployed, when, and who approved it.

CI/CD Fundamentals

Continuous integration

Developers merge small changes to a shared branch frequently — ideally several times a day. Each merge triggers an automated check: does the code pass tests? Does it lint? Is it safe to build?

Continuous delivery

Code that passes CI is automatically prepared for deployment and can be shipped to production through a defined, repeatable process. The team controls when to release, but the path to release is automated.

Continuous deployment

A stricter form of CD where code that passes CI is deployed to production automatically, with no manual approval gate. This is uncommon in government due to change management requirements — continuous delivery is the more realistic target.

The practical result of CI/CD is that deploying becomes routine, boring, and safe. The risk of any single deployment drops because each change is small and well-tested before it ships.


Manual Deployment Risks

Manual deployments have specific failure modes that matter in government contexts:

RiskWhy it matters in government
Human errorA missed step can corrupt data or take a service offline for citizens
No audit trailAuditors and oversight bodies will ask “what was deployed, when, and who approved it?” — you need to be able to answer
InconsistencyTwo manual deploys of the same code may differ because steps were skipped
Slow recoveryIf you can’t deploy quickly, a security patch or critical fix takes hours or days
Hard to roll backIf there’s no artifact from the previous deploy, rolling back may mean rebuilding from memory

Every federal system that handles sensitive data has audit requirements. A manual deployment process cannot meet those requirements reliably.


The Minimum Viable CI Pipeline

For a government web service, a minimum viable CI pipeline includes these checks, in this order:

  1. Linting — Static code analysis. Catches syntax errors, bad patterns, and style violations before a human has to read the code.
  2. Unit tests — Fast, isolated tests that verify individual functions and components work as expected.
  3. Integration tests — Tests that verify components work together — for example, that an API endpoint returns the right data from the database.
  4. Accessibility scan — Automated check using a tool like axe-core that catches common WCAG violations. Required for government systems under Section 508.
  5. Dependency vulnerability scan — A check of your third-party dependencies for known security vulnerabilities. Use OWASP Dependency-Check (free, open-source) or Snyk. Required by most FedRAMP and FISMA controls.
  6. Build artifact creation — The pipeline produces a versioned, reproducible build artifact (a container image, a zip file, a JAR). This artifact is what gets deployed — not the source code.

All six steps must pass before anything moves to staging. A pipeline that only lints is not a real CI pipeline.


The Delivery Pipeline

Once CI passes, the delivery pipeline takes over:

  1. CI passes — All six checks are green.
  2. Artifact promoted to staging — The versioned artifact from CI is deployed to the staging environment automatically.
  3. Automated smoke tests — A lightweight test suite runs against the staging deployment to verify the service is alive and the critical paths work.
  4. Approval gate — A human reviewer (not the author of the code) reviews the change and approves the production deploy. This is the separation of duties control.
  5. Production deploy — The same artifact that ran in staging is deployed to production. No rebuilding, no local machines.
  6. Production health check — Automated monitoring confirms the deployment succeeded before the gate is considered closed.

Figure 1 (placeholder) — A CI/CD pipeline diagram for a government web service. Show left-to-right stages: Developer pushes code → Pull Request created → CI runs (lint, unit tests, integration tests, accessibility scan, security scan) → CI passes → Artifact built and versioned → Deploy to Staging → Automated smoke tests → Approval Gate (requires reviewer sign-off) → Deploy to Production → Production health check. Add a Rollback path from Production back to the previous artifact. Mark the ‘Separation of Duties’ boundary: developer cannot approve their own deploy.


Government-Specific Requirements

Separation of Duties

Federal security controls (NIST 800-53 AC-5) require separation of duties for systems handling sensitive data. In practice, this means the person who writes a piece of code cannot be the only person who approves its production deployment.

In GitHub Actions, implement this with environment protection rules:

  • Create a production environment in GitHub repository settings.
  • Require at least one reviewer who is not the pull request author.
  • Restrict who can approve — not every engineer on the team should have production approval authority.

This is not bureaucracy. This is a control that protects you and your agency from insider threats and from well-intentioned engineers making unreviewed changes to production systems.

Change Windows and Change Management

Many government agencies operate under formal change management processes (ITIL-based or agency-specific). Production deployments outside a defined change window require a formal change request, an approval from a Change Advisory Board (CAB), and documentation.

Your CI/CD pipeline must integrate with this process. Common approaches:

  • Scheduled deployments — Configure your pipeline to deploy to production only during approved change windows. Outside windows, the pipeline deploys to staging and stops.
  • Change ticket integration — Require a change ticket number in the pull request before the production gate opens.
  • Emergency change process — Define and document a pre-approved emergency path for P1 security patches that bypasses the normal window. This path still requires two sets of eyes and produces an expedited change record.

Evidence Capture

Your pipeline must produce evidence that can answer audit questions:

Audit questionPipeline artifact that answers it
What code is in production right now?Artifact version tag + deployment log
When was the last production deployment?Deployment timestamp in the pipeline run log
Who approved the production deployment?Approval record in GitHub environment log
What tests passed before this deploy?CI run log with test results
Did any known vulnerabilities ship?Dependency scan report in the artifact

Store pipeline logs for the retention period required by your agency’s records management policy. For many federal systems, this is 3–7 years.

Secrets Management

Never put secrets in code or CI YAML files. This is true for all software, but government systems carry additional risk: a leaked credential can expose citizen PII, trigger a mandatory breach notification, or compromise a FedRAMP authorization.

Use one of these approaches:

  • GitHub Actions secrets — Encrypted at rest, masked in logs, scoped to a repository or environment. Adequate for most non-sensitive pipelines.
  • HashiCorp Vault — Widely used in government, supports dynamic secrets with automatic rotation, works on-premises and in cloud.
  • Cloud-native secrets managers — AWS Secrets Manager (FedRAMP High authorized), Azure Key Vault (FedRAMP authorized), GCP Secret Manager.

See the Secrets Management guide for detailed setup instructions for each option.


Approval Gates in GitHub Actions

GitHub Actions environment protection rules implement approval gates without custom tooling.

Setup steps

  1. Go to your repository settings → Environments → New environment.
  2. Name it production.
  3. Under “Deployment protection rules,” enable “Required reviewers.”
  4. Add at least two reviewers (individual users or a team). Require at least one approval.
  5. Enable “Prevent self-review” so the pull request author cannot approve their own deployment.
  6. Optionally restrict deployments to specific branches (e.g., only main can deploy to production).

In your workflow file, reference the environment:

deploy-production:
needs: [smoke-tests]
runs-on: ubuntu-latest
environment:
name: production
url: [Your Service Agency](https://your-service.agency.gov)
steps:
- name: Deploy to production
run: ./scripts/deploy.sh production

GitHub will pause the workflow at this job and send a review request to the configured reviewers. The job does not run until a reviewer approves it.


Rollback Strategy

A rollback plan is not optional. Every production deployment needs a defined answer to: “If this breaks, how do we get back to the previous state in under 15 minutes?”

Artifact rollback

Re-deploy the previous versioned artifact. This works when your pipeline kept the previous artifact and your deploy script can target a specific version. This is the most common approach.

Feature flags

Ship the new code but disable the new behavior behind a flag. Rolling back means toggling the flag off, not reverting code. This is safer for schema migrations and high-traffic systems. Tools: LaunchDarkly, Unleash (open-source), environment variables for simple cases.

Blue-green deployment

Maintain two identical production environments (blue = current, green = new). After deploying to green and verifying, switch traffic. Rolling back means switching traffic back to blue. Infrastructure cost is roughly doubled. Commonly used for zero-downtime requirements.

When there is no automated rollback path, document this explicitly in your runbook. Manual rollbacks under pressure take much longer and introduce new errors. If you cannot automate rollback, build that automation before your next major deployment.


Branch Protection Requirements

Configure these branch protection rules on your default branch (main) before writing your first workflow:

RuleWhy
Require pull request reviews before mergingEnsures at least one other engineer reads every change
Require status checks to pass before mergingCI must pass before code enters the shared branch
Require branches to be up to date before mergingPrevents merging stale code that hasn’t been tested against current main
Restrict who can push to mainOnly the pipeline and designated maintainers can push directly
Require signed commitsProves the commit came from the expected identity — required by some FedRAMP controls

These rules apply to everyone, including administrators. Disable the “Allow administrators to bypass” option unless your agency’s policy explicitly requires an exception path.


GitHub Actions Quick-Start — Annotated Pipeline

This is a minimal, annotated pipeline for a Node.js or Go web service. Adapt paths and commands to your project.

.github/workflows/pipeline.yml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# ── Stage 1: Lint ──────────────────────────────────────────────
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
# Fail fast: if linting fails, don't waste time running tests.
# ── Stage 2: Unit and integration tests ───────────────────────
test:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- uses: actions/upload-artifact@v4
with:
name: test-coverage
path: coverage/
# Store the coverage report as evidence.
# ── Stage 3: Accessibility scan ───────────────────────────────
a11y:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci && npm run build
- run: npm run a11y:scan
# axe-core or similar. Fail the pipeline on WCAG violations.
# ── Stage 4: Dependency vulnerability scan ────────────────────
security:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run OWASP Dependency-Check
uses: dependency-check/Dependency-Check_Action@main
with:
project: 'my-agency-service'
path: '.'
format: 'HTML'
out: 'reports'
- uses: actions/upload-artifact@v4
with:
name: dependency-check-report
path: reports/
# Required evidence artifact.
# ── Stage 5: Build versioned artifact ─────────────────────────
build:
needs: [test, a11y, security]
runs-on: ubuntu-latest
outputs:
image-tag: ${{ steps.meta.outputs.tags }}
steps:
- uses: actions/checkout@v4
- name: Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/your-org/your-service
tags: |
type=sha,prefix=,suffix=,format=long
# Tag with the full commit SHA for traceability.
- name: Build and push image
uses: docker/build-push-action@v5
with:
push: ${{ github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
# ── Stage 6: Deploy to staging ────────────────────────────────
deploy-staging:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: staging
steps:
- name: Deploy to staging
run: ./scripts/deploy.sh staging ${{ needs.build.outputs.image-tag }}
- name: Run smoke tests
run: ./scripts/smoke-test.sh [Staging Your Service Agency](https://staging.your-service.agency.gov)
# ── Stage 7: Deploy to production (requires approval) ─────────
deploy-production:
needs: deploy-staging
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: [Your Service Agency](https://your-service.agency.gov)
# GitHub pauses here and sends a review request to configured reviewers.
# The job does not run until an approved reviewer clicks Approve.
steps:
- name: Deploy to production
run: ./scripts/deploy.sh production ${{ needs.build.outputs.image-tag }}
- name: Production health check
run: ./scripts/healthcheck.sh [Your Service Agency](https://your-service.agency.gov)

Tool Options

ToolBest forNotes
GitHub ActionsMost new government teamsFedRAMP authorized on GitHub Enterprise Cloud; large ecosystem of actions
GitLab CITeams that need self-hosted or GitLab’s built-in security scanningGitLab Ultimate has FedRAMP authorization
JenkinsOn-premises government environments where cloud CI is not approvedOlder toolchain; high maintenance burden; still common in DoD
Azure DevOpsMicrosoft shop or Azure Government customersFedRAMP High authorized; native integration with Azure services
TektonKubernetes-native; favored in some DoD Platform One environmentsSteeper learning curve but strong cloud-native story

30/60/90-Day Plan

Build your pipeline in stages. A perfect pipeline that takes six months to build helps no one. A good-enough pipeline that ships in 30 days and improves over time is the goal.

MilestoneWhat to buildSuccess measure
30 daysAutomated tests + basic CI (lint, unit test, build)No unreviewed code merges to main; every PR runs tests
60 daysStaging deploy + approval gateEvery main branch deploy goes to staging first; production requires a second set of eyes
90 daysAutomated rollback + compliance evidence captureCan roll back a bad deploy in under 15 minutes; audit log answers all five evidence questions

Common Mistakes

Lint-only pipelines

Linting without tests is theater. It catches formatting problems but nothing about whether the code works.

No artifact versioning

If you can’t tell what is in production right now (down to the commit SHA), you cannot answer audit questions and you cannot reliably roll back.

Approval gates nobody monitors

A gate that sits open for 72 hours waiting for a reviewer who is on leave is not a real gate. Assign backup reviewers and set notification policies.

No rollback plan

“We’ll figure it out if something goes wrong” is not a rollback plan. Write it down before the first production deployment.

Secrets committed to YAML

Even if you delete the secret from the file later, it is now in your git history. Rotate the secret immediately and use a secrets manager going forward.


Next Steps

  • Branch Protection Guide — Detailed rules for protecting your default branch and preventing direct pushes from bypassing your pipeline.
  • Environment Parity — How to keep staging and development behaving like production so your pipeline tests are meaningful.

Guide 8 of the Engineering Discovery series. Last verified: 2026-05-28.