mazze93/secure-container-template

GitHub: mazze93/secure-container-template

Stars: 1 | Forks: 0

# secure-container-template A minimal Python container template with a hardened default posture: - Non-root container runtime, served by a production WSGI server (gunicorn) - Container `HEALTHCHECK` against the app's `/health` endpoint - GitHub Actions CI with all actions pinned to commit SHAs - SBOM and provenance on published images - Docker Scout vulnerability reporting (best-effort, non-blocking) - SemVer tags, signed release images, and GitHub Releases - Dependabot for Python, Docker base image, and Actions updates ## Repository Layout . ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── release.yml ├── src/ ├── tests/ ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── VERSION ├── build.sh ├── requirements-dev.txt ├── requirements.txt └── test.sh ## Local Development Run the test suite: ./test.sh Build the container: ./build.sh Run the service locally (Flask development server): python3 -m venv .venv source .venv/bin/activate python -m pip install -r requirements.txt python -m src.main The health endpoint is available at `http://127.0.0.1:8000/health`. The container image instead serves the app with **gunicorn** (a production WSGI server), matching `CMD` in the [Dockerfile](Dockerfile). To exercise the image the way CI and production do: ./build.sh docker run --rm -p 8000:8000 secure-container-template:dev curl -s http://127.0.0.1:8000/health # {"status":"ok"} The image declares a `HEALTHCHECK`, so `docker ps` reports container health. ## Optional GitHub Secrets The Docker Scout stages in [.github/workflows/ci.yml](.github/workflows/ci.yml) authenticate to Docker Hub using these repository secrets: - `DOCKERHUB_USERNAME` - `DOCKERHUB_TOKEN` They are **optional**, and both must be set for Scout to run. If either is missing, CI skips the Scout reporting steps and the rest of the pipeline (tests, container build, push, non-root verification) still runs. Scout only executes on pushes to `main`, never on pull requests, and is **best-effort**: a missing or invalid Docker Hub credential logs a warning but never fails the publish job. Set them with GitHub CLI: gh secret set DOCKERHUB_USERNAME --repo mazze93/secure-container-template gh secret set DOCKERHUB_TOKEN --repo mazze93/secure-container-template After secrets are configured, rerun any CI jobs on `main` from the Actions tab. ## CI and Security Gates The main workflow in [.github/workflows/ci.yml](.github/workflows/ci.yml) enforces: - Python tests must pass. - The `Dockerfile` must declare a non-root `USER`. - The built image must have a non-root `Config.User`. - Images published from `main` include SBOM and provenance attestations. - Docker Scout `quickview` and `cves` report on images published from `main`. Non-root execution is a hard merge blocker. Docker Scout CVE findings are reported for visibility but are **non-blocking** by default — to turn fixable `critical`/`high` CVEs back into a merge gate, set `exit-code: true` on the "Docker Scout CVEs" step in [.github/workflows/ci.yml](.github/workflows/ci.yml). ## Published Images Two images are published by the workflows in this repository: | Image | Registry | Published by | Tags | | --- | --- | --- | --- | | `ghcr.io/mazze93/secure-container-template` | GitHub Container Registry | [`ci.yml`](.github/workflows/ci.yml) on push to `main` | `latest`, `sha-`, branch name | | `docker.io/mazze93/secure-container-base` | Docker Hub | [`release.yml`](.github/workflows/release.yml) on `v*.*.*` tags | ``, `sha-` | Both are built with **SBOM** and **provenance** attestations. The release image is additionally **multi-arch** (`linux/amd64`, `linux/arm64`) and **signed with cosign** (keyless / Sigstore). Pull and run the latest CI image: docker run --rm -p 8000:8000 ghcr.io/mazze93/secure-container-template:latest Inspect the attestations on a published image: docker buildx imagetools inspect ghcr.io/mazze93/secure-container-template:latest Verify a release image signature: cosign verify docker.io/mazze93/secure-container-base: \ --certificate-identity-regexp '^https://github.com/mazze93/secure-container-template/' \ --certificate-oidc-issuer https://token.actions.githubusercontent.com ## Branch Protection `main` should require the `test_and_container` status check before merge. That job runs the tests and the non-root image verification, so requiring it turns those checks into a repository policy. Recommended settings for `main`: - Require a pull request before merging - Require at least 1 approval - Require status checks to pass before merging - Require branches to be up to date before merging - Required check: `test_and_container` - Require conversation resolution before merging - Apply rules to administrators Equivalent GitHub CLI call: gh api \ --method PUT \ -H "Accept: application/vnd.github+json" \ /repos/mazze93/secure-container-template/branches/main/protection \ -F required_status_checks.strict=true \ -F required_status_checks.contexts[]=test_and_container \ -F enforce_admins=true \ -F required_pull_request_reviews.dismiss_stale_reviews=true \ -F required_pull_request_reviews.require_code_owner_reviews=false \ -F required_pull_request_reviews.required_approving_review_count=1 \ -F required_conversation_resolution=true \ -F restrictions= \ -F allow_force_pushes=false \ -F allow_deletions=false \ -F block_creations=false \ -F required_linear_history=false \ -F lock_branch=false \ -F allow_fork_syncing=true ## Release Ritual The repository follows SemVer. Releases are created from Git tags matching `v*.*.*`. To ship a new release: echo "0.1.1" > VERSION Update [CHANGELOG.md](CHANGELOG.md), then: git add VERSION CHANGELOG.md git commit -m "release: v0.1.1" git tag v0.1.1 git push origin main --tags Pushing the tag triggers [.github/workflows/release.yml](.github/workflows/release.yml), which creates the GitHub Release.