Compliance Without Tears: DevOps Habits That Stick

compliance

Compliance Without Tears: DevOps Habits That Stick

Practical ways we pass audits without turning engineers into librarians.

Treat Compliance Like a Product Requirement

If we’re honest, most “compliance programs” fail for the same reason unloved internal tools fail: they’re built for auditors, not for the people doing the work. Then we act surprised when teams route around the process like it’s roadworks on the way to the beach.

So let’s flip it. We treat compliance as a product requirement with users (engineers), success metrics (audit outcomes, lead time, incident rate), and iterations (continuous improvement). The goal isn’t to “be compliant”; it’s to build systems where compliant behaviour is the path of least resistance.

Practically, this means we write down what “done” looks like in ways the team can execute: which controls apply, what evidence is required, where it lives, and how it’s generated. If the only place that knowledge exists is a GRC portal with a password nobody remembers, we’ve already lost.

We also align compliance to risk. Not every repo needs the same ceremony. Customer data, payment flows, and identity systems deserve the fancy locks. The internal lunch-menu service (yes, we’ve all had one) can probably survive with basic guardrails.

Finally, we measure the friction. If a control adds a day of waiting, we automate it or rework it. If evidence collection takes a week before an audit, we redesign to generate evidence continuously. Our north star: “Audit-ready by Tuesday,” not “Panic-ready by Q4.”

Useful references when mapping requirements: ISO/IEC 27001, SOC 2 overview, and the NIST Cybersecurity Framework.

Start With Control Mapping (But Keep It Human)

Control mapping sounds like something you’d do with a whiteboard, three consultants, and a pot of coffee the size of a small jacuzzi. We can keep it simpler.

We start by listing our compliance obligations: SOC 2, ISO 27001, PCI DSS, HIPAA—whatever applies. Then we map controls to the systems and processes that actually exist. The trick is to avoid writing “policy theatre.” A control that says “all changes are approved” is useless if our real workflow is “merge when tests pass and two people review.”

A lightweight approach:
1. Pick the framework and version.
2. Identify the in-scope services and data types.
3. Map each control to: an owner, an implementation, and an evidence source.
4. Label the control as automated, semi-automated, or manual.
5. Put it where engineers will see it (repo docs, service catalog), not in a PDF tomb.

We also keep the language plain. Auditors may enjoy the phrase “logical access provisioning,” but our teams respond better to “who can get prod access, and how do we remove it quickly?” We’re not dumbing it down; we’re making it executable.

One more thing: controls drift. Teams change tools, orgs reorg, vendors get swapped. So we schedule periodic “control hygiene” checks—short sessions that confirm the control still matches reality. If it doesn’t, we update the control or change the process. Either way, we stop pretending.

If you want a sanity check on common control categories, CIS Controls are a decent checklist without the legal flavour.

Put Compliance in the Pipeline, Not in Meetings

Meetings are where good intentions go to nap. Pipelines are where behaviour changes. So we embed compliance checks into CI/CD and make them fast, predictable, and visible.

What we typically want from a compliance-friendly pipeline:
– Every change is traceable (commit → PR → build → deploy).
– Tests are repeatable and recorded.
– Security checks run consistently (not just “before the audit”).
– Approvals are enforced when risk is high.
– Deployments are logged with who/what/when.

Here’s a GitHub Actions example that covers a bunch of audit favourites: pinned dependencies, SBOM, SAST, and policy checks. Adjust tools to taste, but keep the shape.

name: ci-compliance

on:
  pull_request:
  push:
    branches: [ "main" ]

permissions:
  contents: read
  security-events: write

jobs:
  build-test-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: "20"

      - name: Install (locked)
        run: npm ci

      - name: Unit tests
        run: npm test -- --ci

      - name: Generate SBOM
        run: npx @cyclonedx/cyclonedx-npm --output-file sbom.json

      - name: Dependency audit
        run: npm audit --audit-level=high

      - name: CodeQL (SAST)
        uses: github/codeql-action/analyze@v3

This gives us repeatable evidence: logs, artifacts, and security findings attached to commits. If a control says “we scan for vulnerabilities,” the pipeline is the proof.

Two practical tips:
– Don’t fail builds on “medium” findings on day one. Start with visibility, then tighten.
– Keep evidence retention in mind. If logs disappear after 7 days, auditors will magically appear on day eight.

Manage Access Like You Actually Mean It

Access control is where compliance meets reality, and reality tends to have shared accounts named “temp-admin2.” We can do better without making everyone file a ticket to breathe.

Our baseline:
– SSO everywhere possible.
– Least privilege by default.
– Time-bound elevation for admin access.
– Automated deprovisioning on offboarding.
– Logs that show who accessed what.

If we’re in AWS, IAM and CloudTrail can do a lot of heavy lifting. The compliance win isn’t “we have IAM,” it’s “we can show access is controlled and reviewed.”

Here’s a Terraform snippet that forces MFA for sensitive actions (simplified, but you get the idea). The point is: encode the rule, don’t rely on memory.

data "aws_iam_policy_document" "deny_without_mfa" {
  statement {
    sid    = "DenyAllExceptListedIfNoMFA"
    effect = "Deny"

    not_actions = [
      "iam:CreateVirtualMFADevice",
      "iam:EnableMFADevice",
      "iam:GetUser",
      "iam:ListMFADevices",
      "sts:GetSessionToken"
    ]

    resources = ["*"]

    condition {
      test     = "BoolIfExists"
      variable = "aws:MultiFactorAuthPresent"
      values   = ["false"]
    }
  }
}

resource "aws_iam_policy" "enforce_mfa" {
  name   = "enforce-mfa"
  policy = data.aws_iam_policy_document.deny_without_mfa.json
}

We also schedule access reviews, but we don’t do them in spreadsheets from 2009. We automate reports, we make managers attest in a workflow tool, and we keep the results somewhere auditable.

For teams needing a model, OWASP ASVS is a solid reference for auth/access expectations, and it maps nicely to real engineering tasks.

Make Evidence a Side Effect, Not a Project

The worst compliance pattern is “evidence week,” where everyone scrambles to assemble screenshots like they’re building a scrapbook for an auditor’s birthday. Instead, we design systems that emit evidence continuously.

What counts as evidence?
– Change history (PRs, approvals, tickets linked to deploys).
– Test results and build logs.
– Security scan outputs.
– Access logs and admin actions.
– Incident records and postmortems.
– Policy acknowledgements (when required).

We pick a small set of systems of record:
– Git provider for code and reviews.
– CI/CD for builds and deployment logs.
– Cloud audit logs for access and infra changes.
– Ticketing for exceptions and risk acceptance.
– A document store for policies and runbooks.

Then we standardise where evidence lives and how long it’s kept. Retention is the quiet killer: if we only keep CI artifacts for 14 days, we’ll be “compliant” for exactly two weeks.

We also define an “evidence map” per control: one link to the source, one sentence on how it’s generated, and one note on retention. That’s it. No novels.

A fun side effect: once evidence is always available, teams stop fearing audits. Auditors ask questions; we paste links; everyone goes home at a reasonable hour. It’s not glamorous, but neither is a 9 p.m. screenshot session.

Handle Exceptions Without Turning Into the Policy Police

Exceptions happen. A legacy system can’t support encryption at rest (yet). A vendor won’t sign the clause. A team needs temporary access for an incident. The problem isn’t exceptions; the problem is undocumented exceptions that become permanent residents.

We run exceptions like we run incidents: visible, time-bound, and owned.

Our exception workflow usually includes:
– What control is impacted and why.
– Risk description in plain language.
– Compensating controls (what reduces the risk right now).
– Owner and expiry date.
– Approval from the right level (not always security; sometimes product/risk).

Importantly, we make exceptions easy to request. If the process is painful, teams will “forget” to do it. We’d rather have a quick form and an honest record than a perfect policy nobody follows.

We also review exceptions regularly. If the same exception keeps getting renewed, it’s not an exception—it’s tech debt with a fancy hat. That’s when we prioritise remediation work and track it like any other delivery commitment.

Auditors generally don’t demand perfection; they demand that we understand risk and manage it. A well-documented exception with a remediation plan often looks better than a hand-wavy claim that everything is perfect. Nothing is perfect. Not even our Terraform modules. Especially not our Terraform modules.

Build a Culture That Doesn’t Hate Compliance

Culture isn’t posters and slogans; it’s what happens when nobody’s watching. If compliance is seen as a tax, people will minimise it. If it’s seen as part of quality, teams will improve it.

We do a few small things that compound:
– Teach controls through real examples (“this is why we require reviews”) instead of abstract lectures.
– Make secure defaults the standard templates (repos, pipelines, cloud accounts).
– Celebrate fixes that reduce risk and reduce toil.
– Keep security and compliance folks embedded and approachable—less “gatekeeper,” more “pairing partner.”

We also avoid using compliance as a stick. If someone reports an issue, they shouldn’t get punished for honesty. We want early signals, not perfect silence until an auditor finds it.

One practical habit: include compliance checks in definition-of-done for high-risk changes, and keep the checklist short. Nobody needs a 42-item PR template. We’re engineers, not medieval scribes.

And yes, we keep humour alive. When someone says “the auditor wants proof,” we say “cool, let’s send them a link, not a collage.” The goal is calm professionalism—plus the occasional eye-roll at bureaucracy, because we’re human.

Share