“Six Gates to Production”

Most CI/CD templates stop at build-and-deploy. This one treats every commit as untrusted until six independent security tools have examined it. It is the pipeline I wish every client already had.

What problem this solves

A container image landing in production without being scanned is a liability. But bolting a scanner onto an existing pipeline is fragile: the scanner runs, the output gets ignored, and the deployment continues anyway. This template makes security gates non-negotiable. A critical finding in Semgrep, Trivy, or any other tool blocks the pipeline. A warning gets logged to a SARIF report. The deployment only proceeds when every gate passes.

Architecture

Every commit that lands on this repo goes through a six-stage checkpoint before it sees production. Think of it as airport security for code – except the scanners actually catch things, and nobody has to take off their shoes.

flowchart LR A[Developer pushes code] --> B[Lint and SAST] B --> C[Dependency and secret scan] C --> D[Build and image scan] D --> E[Policy check] E --> F[Deploy to staging] F --> G[Deploy to production] style A fill:#1b1c1d,stroke:#6366f1,color:#e0e0e0 style B fill:#1b1c1d,stroke:#f59e0b,color:#e0e0e0 style C fill:#1b1c1d,stroke:#f59e0b,color:#e0e0e0 style D fill:#1b1c1d,stroke:#f59e0b,color:#e0e0e0 style E fill:#1b1c1d,stroke:#f59e0b,color:#e0e0e0 style F fill:#1b1c1d,stroke:#10b981,color:#e0e0e0 style G fill:#1b1c1d,stroke:#10b981,color:#e0e0e0

If a commit fails any of those yellow gates, it stops right there. The pipeline does not proceed. The developer gets a report explaining what was found and what to fix. Only green gets to production.

Each stage is a hard gate. Severity thresholds are configurable: CRITICAL and HIGH findings block the pipeline. MEDIUM findings generate warnings in the SARIF report. LOW and INFO are logged only.

The six gates

Semgrep runs static analysis on every push. It catches tainted input flows, hardcoded secrets patterns, and language-specific anti-patterns. Rules are in .semgrep.yml and tuned for Python/Flask.

Trivy scans dependencies and the final container image for known CVEs. It also scans Terraform configurations for misconfigurations. Combined, it catches both application-level and infrastructure-level vulnerabilities.

Hadolint lints the Dockerfile. It enforces best practices: no latest tags, no root user, COPY instead of ADD, pinned package versions.

Gitleaks runs as a pre-commit hook and in CI. It detects credentials committed by accident: AWS keys, GitHub tokens, database passwords. Pre-commit catches them before they hit the remote.

Checkov scans Terraform for cloud misconfigurations: unencrypted S3 buckets, open security groups, missing flow logs, overly permissive IAM policies.

OPA/Conftest enforces organization-specific rules written in Rego. No off-the-shelf scanner can verify that every service uses your company’s approved base image or that all S3 buckets have a specific tag for billing. OPA makes these rules enforceable.

Infrastructure layer

The Terraform modules provision an AWS ECS Fargate environment with private subnets, VPC flow logs, encrypted S3 buckets, and least-privilege IAM roles. For local deployments, Docker Compose with Nginx handles TLS termination, HTTP-to-HTTPS redirection, rate limiting, and security headers. The app container runs as a non-root user with a read-only filesystem.

Design decisions

Nginx as the TLS terminator for local deployments instead of a cloud load balancer. This lets you test the full security posture (TLS, rate limiting, headers) on a laptop before touching AWS.

Non-root, read-only containers everywhere. The Flask app runs as UID 1000. The filesystem is mounted read-only except for /tmp. This limits what an attacker can do even if they compromise the application.

Hard fail on critical, warn on medium. False positives are real. Blocking a deployment on a medium-severity finding that turns out to be a false positive causes alert fatigue. Critical and high findings get the hard gate. Medium generates a SARIF report visible in the repository’s Security tab.

View on GitHub