YourBOX CI/CD
Purpose
Section titled “Purpose”The 2020 YourBOX pipeline runs three stages (test, init, plan, apply,
build, deploy) targeting Azure only, with the test job pinned to PHP 8.1 and
no security gate. The modernization rewires the pipeline to:
- Consume the reusable components from
gitlab-ci-templatesfor lint, test, security and build. - Pass the Plumber supply chain controls from ADR-0009.
- Produce one image consumed by every deployment target documented in ADR-0010.
- Emit DORA metrics into the workspace dashboards.
Pipeline Stages
Section titled “Pipeline Stages”flowchart LR Lint[Lint] Test[Test] Build[Build image] Scan[Container hardening] Sign[Sign image] SmokeCompose[Smoke compose] DeployVps[Deploy VPS] E2eVps[E2E VPS] DeployAzure[Deploy Azure] DeployK8s[Deploy Kubernetes] Dora[DORA metrics]
Lint --> Test --> Build --> Scan --> Sign --> SmokeCompose --> DeployVps DeployVps --> E2eVps --> DeployAzure --> DeployK8s --> DoraThe pipeline is linear by default. Merge requests stop at the smoke compose
stage. Deploy stages run only on the protected main branch.
Reusable Components
Section titled “Reusable Components”YourBOX consumes the components from gitlab-ci-templates through immutable
references. The component repository owns the version contract; the application
repository selects a version per included file.
include: - project: yourbox/gitlab-ci-templates ref: v1.4.0 file: /templates/lint.gitlab-ci.yml - project: yourbox/gitlab-ci-templates ref: v1.4.0 file: /templates/test.gitlab-ci.yml - project: yourbox/gitlab-ci-templates ref: v1.4.0 file: /templates/security.gitlab-ci.yml - project: yourbox/gitlab-ci-templates ref: v1.4.0 file: /templates/build-image.gitlab-ci.yml - project: yourbox/gitlab-ci-templates ref: v1.4.0 file: /templates/dora.gitlab-ci.yml - project: external-tools/plumber ref: v0.3.4 file: /templates/plumber.yml inputs: config_file: ".plumber.yaml" threshold: 100 output_file: "plumber-report.json" fail_warnings: true verbose: true branch: $CI_COMMIT_REF_NAMEThe current gitlab-ci-templates repository ships Bun and Node templates only.
A PHP and Symfony variant is added under templates/test.gitlab-ci.yml to host
the YourBOX test job patterns. The variant is documented in the
gitlab-ci-templates README and reviewed before consumption.
Lint Stage
Section titled “Lint Stage”The lint stage runs five jobs in parallel:
lint:php-cs-fixerrunsvendor/bin/php-cs-fixer fix --dry-run --diff.lint:phpstanrunsvendor/bin/phpstan analyse --memory-limit=512Mat level 8.lint:hadolintrunshadolint Dockerfileand the sidecar nginx and php-fpm Dockerfiles if any.lint:yamlrunsyamllint -d relaxed .across the workspace.lint:gitleaksrunsgitleaks detect --no-git --source .to catch staged secrets.
Each job uses a digest pinned image per ADR-0009. Failures block the pipeline.
Test Stage
Section titled “Test Stage”The test stage runs three jobs:
test:unitrunscomposer test:unitagainst an empty PHP container. No database, no storage. The job completes in under 30 seconds.test:integrationbrings up a digest pinned PostgreSQL service and runscomposer test:integration. The job seeds the schema with the Doctrine migrations, applies the data fixtures and exercises the repository ports.test:functionalboots the Symfony kernel throughWebTestCaseand exercises the public and admin routes against a SQLite kernel.
A coverage gate fails the pipeline below 70 percent overall on app/src/, with
90 percent on Domain/ and Application/.
test:integration: image: docker.io/library/php:8.5-cli-alpine@sha256:<digest> services: - name: docker.io/library/postgres:16-alpine@sha256:<digest> alias: postgres variables: DB_DSN: "postgresql://postgres:postgres@postgres:5432/yourbox_test" before_script: - apk add --no-cache git zip unzip ffmpeg - composer install --no-interaction --no-progress - php bin/console doctrine:database:create --if-not-exists -e test - php bin/console doctrine:migrations:migrate --no-interaction -e test script: - composer test:integration coverage: '/^\s*Lines:\s+(\d+\.\d+)%/' artifacts: when: always paths: - build/coverage.xml reports: coverage_report: coverage_format: cobertura path: build/coverage.xmlBuild And Scan
Section titled “Build And Scan”The build stage runs in a Kaniko or Buildah job (rootless) because the GitLab
runner does not have privileged Docker access. The output image is pushed to
registry.yourbox.site/yourbox:<commit_sha> and is also tagged with the
semantic version on release branches.
The scan stage runs Trivy against the freshly built image:
scan:trivy: image: name: docker.io/aquasec/trivy:0.70.0-amd64@sha256:<digest> entrypoint: [""] needs: - build:image script: - trivy image --severity HIGH,CRITICAL --exit-code 1 \ registry.yourbox.site/yourbox:$CI_COMMIT_SHA artifacts: when: always paths: - trivy.jsonA scan:gitleaks job runs against the built image filesystem for defense in
depth. A scan:sbom job generates a CycloneDX SBOM and stores it as an artifact.
Image Signature
Section titled “Image Signature”The sign stage attaches a cosign signature and a CycloneDX SBOM attestation to
the image. The signing key lives in OpenBao under kv/yourbox/cosign-key and is
fetched at job time through the JWT auth method.
sign:image: image: name: docker.io/anchore/sbom-action:0.17.0@sha256:<digest> entrypoint: [""] needs: [scan:trivy] script: - cosign sign --key env://COSIGN_KEY \ registry.yourbox.site/yourbox@$IMAGE_DIGEST - cosign attest --predicate sbom.json --type cyclonedx --key env://COSIGN_KEY \ registry.yourbox.site/yourbox@$IMAGE_DIGESTThe homelab Kubernetes cluster enforces the cosign signature through a Sigstore policy controller. An unsigned image is rejected by admission.
Plumber Gate
Section titled “Plumber Gate”The Plumber job runs early in the pipeline as a lint stage entry. The score
threshold is 100, which fails on any control regression. A failure produces a
plumber-report.json artifact and a merge request widget with the breakdown.
The current Plumber controls active for YourBOX are listed on the Verification Baseline page. They include the digest pinning rules, the registry allowlist, the protected branches rules and the includes freshness rules.
DORA Metrics
Section titled “DORA Metrics”The dora.gitlab-ci.yml template emits the deployment frequency, lead time for
changes, mean time to restore service and change failure rate signals. The
metrics flow to the Grafana dashboards documented on the Observability page.
Deploy Stages
Section titled “Deploy Stages”Per ADR-0010 the same image deploys to four targets in order. Each deploy stage
runs only on the protected main branch, requires manual approval and emits a
GitLab Release on success.
| Stage | Target | Surface |
|---|---|---|
| SmokeCompose | docker compose CI | smoke test on every merge to main |
| DeployVps | VPS via Ansible | public smoke test, manual approval |
| E2eVps | VPS | Cypress E2E suite against the VPS URL |
| DeployAzure | Azure App Service | staging, manual approval |
| DeployK8s | Homelab k8s | Argo CD reconciliation after manifest MR |
Argo CD does not pull from GitLab directly. The deploy step opens a Merge
Request against homelab-platform/kubernetes/apps/yourbox/ updating the image
digest in the Helm release. A reviewer merges the change and Argo CD picks it
up. The pattern matches the weather-app delivery flow.
Failure Surface
Section titled “Failure Surface”- A lint or test failure blocks the pipeline before the build stage.
- A scan failure blocks the deploy stages and reports the CVE list as a job artifact.
- A Plumber failure blocks every stage that runs after the lint stage.
- A deploy failure on an environment freezes the next environments and posts to the GitLab MR with the runbook link.