TL;DR
- GitLab CI/CD becomes the central orchestrator of your delivery workflow: clearly structured stages (build, test, package, deploy) make your artifact chain traceable and verifiable.
- Rootless, daemonless container builds with Kaniko or Buildah enable secure pipelines without privileged Docker daemons – including SBOM generation as a basis for auditability and upcoming requirements from NIS2 and Cyber Resilience Act (effective from 09.01.2024).
- A central registry like Harbor evolves from a simple image store to a compliance hub: automatic CVE scanning, image signing, and enforced vulnerability policies measurably increase your supply chain security.
- With HashiCorp Vault and the External Secrets Operator, you automate secrets management from the pipeline to the /kubernetes/ cluster – including dynamic credentials and rotation, without "hiding" secrets in GitLab variables.
- ayedo provides an integrated platform with GitLab, Harbor, HashiCorp Vault, and ESO along with proven GitLab CI/CD templates that pre-structure this workflow for you.
GitLab CI/CD as the Backbone of the Deployment Workflow
For modern, regulation-compliant software delivery, a clean CI/CD pipeline is not "nice to have" but the technical organizational chart of your organization. In many companies, GitLab assumes this role – in the ayedo platform, it is the first step in the end-to-end workflow.
Repository Setup with a View to Compliance
An application repository should already reflect the later automation:
- Application code (e.g.,
src/for a Django app) - Tests (unit, integration, and where appropriate, security tests)
- Dockerfile for the container build
- Chart directory for later packaging in Helm/ohMyHelm
- A
.gitlab-ci.ymlthat maps the entire lifecycle of the application
The exact folder structure is less important than the clear separation of responsibilities: code, infrastructure definition, and delivery logic remain distinguishable. This facilitates audits and reduces the risk of "hidden" configurations.
Stages: build, test, package, deploy – Deliberately Separated
A typical pipeline for a Django application can be divided into four main stages:
- test: Quality assurance
- build: Build and sign the container image
- package: Package the application (e.g., Helm chart with referenced image version)
- deploy: Handover to the deployment system (in Part 1 up to Harbor; ArgoCD follows in Part 2)
This separation creates transparency: An auditor or a new team lead can immediately see where tests take place, when images become immutable, and where security checks apply.
Furthermore, approval processes can be implemented along these stages: for example, only on the main branch do full pipelines run up to and including package and deploy, while feature branches are limited to test and possibly build. This way, you combine developer productivity with clear control points.
Secure Container Builds: Kaniko vs. Buildah, Dockerfile Discipline, and SBOM
The days when a privileged Docker daemon ran in the CI runner should be over – not only for security reasons but also because many compliance frameworks critically view the operation of privileged services in shared environments.
Rootless, Daemonless: Why Kaniko or Buildah?
Both Kaniko and Buildah are designed to build container images without a running Docker daemon and without root rights in the container:
- Kaniko
- Specifically developed for container platforms, ideal for runners on /kubernetes/.
- Uses the registry for caching and works directly on the filesystem.
-
Very good integration into GitLab CI/CD, lean and stable.
-
Buildah
- Offers a very powerful CLI and supports complex build scenarios.
- Can be used Dockerfile-based or script-based.
- Good if you already have Podman/Buildah established in your company.
Both approaches significantly reduce the attack surface of your runners. This is particularly relevant if you want to meet the requirements of NIS2 in the future (effective from 18.10.2024), where secure operating processes and risk minimization are explicitly required.
Dockerfile Best Practices as a Compliance Building Block
A secure image does not start with the scanner but in the Dockerfile. For a Django application, the following principles have proven effective:
- Minimalistic base images (e.g., distroless, slim variants) instead of fully equipped distributions
- Multi-stage builds to keep build dependencies out of the final runtime image
- Non-root user in the container, consistently used
- Explicit version pins for operating system packages and Python dependencies
- Reproducibility: deterministic builds where a commit always produces the same image
These patterns are not a matter of style but significantly ease security assessments: fewer packages mean fewer potential CVEs, and clear version specifications make it easier to respond to advisories and scans.
SBOM Generation: Making the Supply Chain Visible
Another building block towards a regulation-compliant supply chain is the Software Bill of Materials (SBOM). It describes which components (e.g., Python packages, system libraries) are contained in an image.
In the pipeline, the SBOM step should be closely linked to the container build:
- SBOM generation directly from the final image
- Use of established formats like CycloneDX or SPDX
- Storage of the SBOM as an artifact and ideally as an attachment in the registry metadata model
This creates a foundation to respond to future regulatory requirements – for example, when customers or regulatory authorities request detailed information on your software's component list under the Cyber Resilience Act.
Harbor Registry: From Image Storage to Security Gateway
A modern registry like Harbor is much more than a place where latest tags are "parked". In a well-designed CI/CD landscape, Harbor becomes the security and compliance gateway between build and deployment.
Image Push as an Explicit Handover Point
In the build stage of the pipeline, the image is created with Kaniko or Buildah and then pushed into a dedicated project in Harbor. Typically, at least one immutable tag (e.g., commit hash or build number) and optionally a "movable" tag like latest are created.
This push is a clear handover point: Before, the image is a temporary result of a build; afterward, it is a versioned artifact that may be used by production systems – but only after it has passed the defined controls in Harbor.
Automatic CVE Scanning and Policies
Harbor integrates vulnerability scanners and automatically performs image scanning after each push. For a Django app based on a Python or Debian/Ubuntu base image, both OS packages and runtime libraries are captured.
The key strength lies in the policies:
- They define which severity levels (e.g., "High" and "Critical") are tolerated.
- They can specify that images with certain vulnerabilities cannot be pulled or promoted in "Production" projects.
- They can establish a model where known but not yet fixable vulnerabilities are specifically accepted and documented (e.g., via allow lists with justification and time limit).
This builds a bridge between the security team and delivery teams: Not every finding immediately blocks the pipeline, but nothing goes through silently.
Image Signing as a Supply Chain Guardrail
In addition to scanning, image signing is gaining significant importance. Harbor supports modern signature mechanisms (e.g., Notary v2, cosign) and can enforce signatures as a prerequisite for certain actions.
A practical model:
- The build stage signs each successfully built image with a technical key.
- Harbor only accepts pulls into sensitive target projects for signed images.
- Later in the workflow (in ArgoCD, Part 2 of the series), only signed images are deployed.
This creates a cryptographically secured chain: Only images that actually originate from your GitLab pipeline reach the production clusters. This is a central building block to effectively prevent supply chain attacks such as the introduction of foreign images.
Secrets Management with HashiCorp Vault and ESO: Preparing for Operation
Even though this first part of the series ends with the push to Harbor, the topic of secrets already belongs in this section – otherwise, credentials unnecessarily end up in GitLab variables or even in repositories.
Vault as a Central Instance for Credentials
HashiCorp Vault serves as the central hub in the ayedo platform for:
- Database credentials (e.g., PostgreSQL for the Django app)
- API tokens, certificates, and TLS material
- Short-lived credentials for builds (e.g., authentication against Harbor)
Instead of storing fixed username/password pairs in GitLab, the pipeline retrieves short-lived tokens or dynamic credentials from Vault. This significantly reduces the risk of leaked secrets while simultaneously meeting common requirements for least privilege and rotation.
ESO: Automatic Sync to the Kubernetes Cluster
The External Secrets Operator is the link between Vault and your /kubernetes/ cluster. It automatically synchronizes secrets from Vault into native Kubernetes secrets, which can then be used by deployments.
Even though the actual rollout in this article series is only covered in the second part with ArgoCD, you should already ensure:
- All runtime-critical secrets of the Django app are in Vault, not in GitLab.
- ESO configurations are prepared so that no manual "secret YAMLs" are required during the first deployment.
- The roles and rights models in Vault are aligned: developers, CI/CD, and platform teams have exactly the rights they need – no more, no less.
This creates a supply chain where artifacts (images, charts) and configuration (secrets) are clearly separated but technically well-integrated.
Practical Pipeline Design for a Django Application
What does this look like concretely – but without YAML – for a Django app?
Stage "test": Quality and Basic Security
In the test stage, typically:
- Installation of Python dependencies from defined requirements files
- Django test suite with unit and integration tests against a temporary database
- Linting (e.g., with flake8, isort, black – in check mode)
- Optionally first SAST or dependency checks
It is important that this stage runs on all relevant branches and remains fast enough not to slow down developer feedback.
Stage "build": Image, SBOM, Signature
The build stage takes over:
- Container build with Kaniko or Buildah based on the Dockerfile
- Tagging with a unique build ID (e.g., commit hash and pipeline ID)
- Generation of an SBOM for the finished image
- Signing the image with a technical key
- Push into a dedicated Harbor project for builds
At the end of this stage, there is a clearly identifiable, signed, and SBOM-equipped image in Harbor – the foundation for any further use.