DevOps

Building a Developer Platform on Kubernetes: An Internal PaaS for Engineering Teams

Ruslan Ismailov Published 14 min read
B

Introduction: What Is an Internal Developer Platform and Why It Matters in 2026

In 2026, the gap between business velocity and software delivery speed has become a critical competitive factor. Teams that deploy dozens of times per day outpace those waiting weeks for a release. This is where the Internal Developer Platform (IDP) comes in — an internal PaaS that abstracts developers from Kubernetes, cloud infrastructure, and routine operations.

An IDP is not a single tool. It's a set of standardized tools, processes, and self-service interfaces that allow a developer to deploy a new microservice, spin up a test environment, and connect secrets — without ever reaching out to the DevOps team. According to the DORA Report 2024, organizations with mature platform engineering deliver changes 2.5x faster and experience 4x fewer rollbacks.

"The platform is a product. Developers are its users. Poor DX kills velocity just as much as technical debt."

In this article, we'll walk through how to build a fully functional internal PaaS on top of Kubernetes, which tools to use, how to standardize CI/CD for Go and PHP services, and what a step-by-step plan looks like for teams of 10 to 50 people.

Key Components of an Internal Developer Platform

A fully-featured IDP consists of several layers, each addressing a specific developer pain point:

  • Self-service deployment — a developer clicks a button or runs a git push, and the platform automatically creates a namespace, deploys the service, and configures ingress.
  • Service Templates — a golden path for new Go, PHP, or Node.js microservices: repository, CI/CD, Dockerfile, and Helm chart are all generated from a template.
  • Environment management — dev, staging, production, and isolated preview environments for every PR.
  • Secrets management — integration with Vault or Kubernetes Secrets via the External Secrets Operator.
  • Observability out-of-the-box — logs, metrics, and traces are connected automatically when a new service is deployed.

Tooling: Backstage as the Developer Portal, Crossplane for Infrastructure

Backstage — The Platform Control Center

Backstage by Spotify is an open-source developer portal that has become the de facto standard for IDPs. It solves the classic questions: "Where's the documentation?", "What services do we have?", "Who owns this repository?".

In the context of a Kubernetes platform, Backstage serves the following functions:

  • Software Catalog — a catalog of all microservices, their dependencies, owners, and deployment status.
  • Software Templates — interactive forms for creating a new service along the golden path: the developer enters the service name, picks a language (Go/PHP), clicks "create," and gets a repository with CI/CD, a Helm chart, and an ArgoCD Application.
  • TechDocs — documentation surfaced directly in the portal, built from markdown files in repositories.
  • Kubernetes Plugin — displays pod, deployment, and service status right inside the service card.

Crossplane — Infrastructure as Code at the Platform Level

Crossplane enables management of cloud resources (RDS, S3, CloudSQL) through Kubernetes resources. A developer creates a PostgreSQLInstance object in their namespace — Crossplane provisions the database in AWS/GCP and delivers the connection string via a Kubernetes Secret.

This is the cornerstone of self-service infrastructure: the development team doesn't write Terraform or open the AWS console — everything goes through familiar kubectl commands or the Backstage UI.

CI/CD as Part of the Platform: Automated Pipelines for Go and PHP

CI/CD in an IDP is more than just pipelines. It's a set of standardized, versioned pipeline templates maintained centrally by the platform team and consumed by developers without modification.

Here's an example GitLab CI structure for a Go service using a shared platform template:

# .gitlab-ci.yml in the Go service repository
include:
  - project: 'platform/ci-templates'
    ref: 'v2.1.0'
    file: '/go-service.yml'

variables:
  SERVICE_NAME: "payment-service"
  GO_VERSION: "1.22"
  REGISTRY: "registry.company.internal"
  HELM_CHART_VERSION: "1.4.0"

The go-service.yml template on the platform side implements a full pipeline:

# platform/ci-templates/go-service.yml
stages:
  - test
  - build
  - push
  - deploy-preview
  - deploy-staging
  - deploy-production

test:
  stage: test
  image: golang:${GO_VERSION}
  script:
    - go test ./... -race -coverprofile=coverage.out
    - go vet ./...
  coverage: '/coverage: (\d+\.\d+)% of statements/'

build-image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t ${REGISTRY}/${SERVICE_NAME}:${CI_COMMIT_SHA} .
    - docker push ${REGISTRY}/${SERVICE_NAME}:${CI_COMMIT_SHA}

deploy-preview:
  stage: deploy-preview
  script:
    - argocd app create ${SERVICE_NAME}-pr-${CI_MERGE_REQUEST_IID}
        --repo ${CI_PROJECT_URL}
        --path helm
        --dest-namespace preview-${CI_MERGE_REQUEST_IID}
        --helm-set image.tag=${CI_COMMIT_SHA}
  only:
    - merge_requests

An equivalent template exists for PHP services — with composer install and phpunit steps, plus Laravel-specific configuration cache optimizations.

Microservice Deployment Standardization: Helm, Kustomize, and GitOps with ArgoCD

A Unified Helm Chart for All Microservices

One of the core architectural decisions in an IDP is a single base Helm chart for all microservices. It lives in the platform repository and includes:

  • Deployment with readiness/liveness probes
  • HorizontalPodAutoscaler
  • PodDisruptionBudget
  • ServiceMonitor for Prometheus
  • NetworkPolicy
  • Ingress with automatic TLS via cert-manager

Each microservice only overrides the values it needs in its own values.yaml:

# values.yaml for a specific service
service:
  name: payment-service
  port: 8080

image:
  repository: registry.company.internal/payment-service
  tag: "latest"

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

env:
  - name: DB_HOST
    valueFrom:
      secretKeyRef:
        name: payment-db-secret
        key: host

GitOps with ArgoCD

ArgoCD implements the GitOps approach: cluster state always matches what's declared in Git. The infrastructure repository follows this structure:

gitops-repo/
├── apps/
│   ├── production/
│   │   ├── payment-service/
│   │   │   └── values.yaml
│   │   └── user-service/
│   │       └── values.yaml
│   ├── staging/
│   └── preview/
├── argocd-apps/
│   ├── production.yaml
│   └── staging.yaml
└── platform/
    ├── cert-manager/
    ├── external-secrets/
    └── monitoring/

ArgoCD ApplicationSet automatically creates an Application for each service directory:

apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
  name: production-services
spec:
  generators:
    - git:
        repoURL: https://git.company.internal/gitops-repo
        revision: main
        directories:
          - path: apps/production/*
  template:
    spec:
      project: production
      source:
        repoURL: https://git.company.internal/gitops-repo
        targetRevision: main
        path: '{{path}}'
        helm:
          valueFiles:
            - values.yaml
      destination:
        server: https://kubernetes.default.svc
        namespace: '{{path.basename}}'
      syncPolicy:
        automated:
          prune: true
          selfHeal: true

Environment Management: Preview Environments for Every PR

Preview environments are one of the most valuable elements of developer experience. Every Pull Request automatically gets an isolated environment accessible at a URL like https://payment-service-pr-142.preview.company.internal.

The architecture works as follows:

  1. The CI/CD pipeline builds a Docker image and pushes it to the registry tagged as pr-{number}.
  2. The pipeline creates a Kubernetes namespace preview-142.
  3. ArgoCD creates an Application using the Helm chart, overriding the image tag and hostname.
  4. cert-manager issues a TLS certificate for the preview domain.
  5. When the PR is closed or merged, the namespace and ArgoCD Application are automatically deleted.

For managing the lifecycle of preview environments, consider using Argo CD Ephemeral Access or a custom Kubernetes Operator that watches MR status via the GitLab/GitHub API.

Docker Registry Integration and Secrets Management

Docker Registry

The platform should provide a single internal Docker Registry. Popular options include Harbor (open-source, with vulnerability scanning), GitLab Container Registry, or AWS ECR. Harbor is recommended for on-premise deployments: it supports RBAC, cross-region replication, and Trivy integration for image scanning.

Each service gets a dedicated project in Harbor, with access managed through OIDC integration with the corporate identity provider.

Secrets Management: Vault + External Secrets Operator

The gold standard for secrets on a Kubernetes platform is HashiCorp Vault + External Secrets Operator (ESO). A developer declares which secret their service needs via an ExternalSecret object:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: payment-db-secret
  namespace: payment-service
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: vault-backend
    kind: ClusterSecretStore
  target:
    name: payment-db-secret
    creationPolicy: Owner
  data:
    - secretKey: host
      remoteRef:
        key: secret/production/payment-service/database
        property: host
    - secretKey: password
      remoteRef:
        key: secret/production/payment-service/database
        property: password

ESO automatically syncs secrets from Vault into Kubernetes Secrets and refreshes them on rotation. Developers never see secret values — only their structure.

Platform Metrics: DORA Metrics and Developer Experience

A platform without metrics is a platform flying blind. Key metrics fall into two categories:

DORA Metrics for Delivery Assessment

  • Deployment Frequency — how often the team deploys to production. Target for high performers: multiple times per day.
  • Lead Time for Changes — time from first commit to production. Target: under 1 hour.
  • Change Failure Rate — percentage of deployments that cause an incident. Target: under 5%.
  • Mean Time to Recovery (MTTR) — time to recover from a failure. Target: under 1 hour.

Developer Experience Metrics

  • Time from new repository creation to first staging deployment (onboarding time).
  • Time to create a preview environment for a PR.
  • Percentage of services using golden path templates.
  • Quarterly developer NPS surveys on the platform.

For metrics collection, use a combination of Prometheus + Grafana for technical metrics and the Backstage Insights plugin for DX metrics. DORA metrics can be automatically calculated from GitLab/GitHub data using the Liatrio DORA plugin for Backstage.

Step-by-Step Plan for Building an IDP from Scratch for Teams of 10–50

Building a platform is an iterative process. Don't try to do everything at once. Here's a pragmatic roadmap:

Phase 1: Foundation (Months 1–2)

  1. Deploy a production-ready Kubernetes cluster (EKS/GKE or kubeadm on bare metal).
  2. Set up a unified Docker Registry (Harbor).
  3. Deploy ArgoCD and migrate the first 2–3 services to GitOps.
  4. Create a base Helm chart for microservices.

Phase 2: CI/CD Standardization (Months 2–3)

  1. Create shared CI/CD templates for Go and PHP services.
  2. Introduce the External Secrets Operator + Vault.
  3. Configure cert-manager for automatic TLS certificates.
  4. Launch the first preview environments.

Phase 3: Developer Portal (Months 3–5)

  1. Deploy Backstage and populate the Software Catalog.
  2. Create Software Templates for common service types (Go API, PHP/Laravel Worker).
  3. Connect the Kubernetes and CI/CD plugins to Backstage.
  4. Set up TechDocs for internal documentation.

Phase 4: Self-Service Infrastructure (Months 5–7)

  1. Introduce Crossplane for self-service databases and queues.
  2. Set up DORA metrics dashboards in Grafana.
  3. Run the first quarterly DX survey.
  4. Iterate based on developer feedback.

Conclusion and Common Mistakes

Building an Internal Developer Platform is an investment that pays off within 6–12 months for teams of 10 or more. Key principles for a successful platform:

  • Platform as a Product — treat the platform like a product with a roadmap, SLAs, and developer feedback loops.
  • Golden Path, Not Golden Cage — the platform should offer a convenient default path without blocking non-standard use cases.
  • Incremental Adoption — migrate services gradually; don't try to rewrite everything in a single sprint.

Common mistakes when building an IDP:

  • Over-engineering from day one — a 15-person team doesn't need Crossplane and a service mesh at the start.
  • Ignoring DX — technical excellence in the platform is worthless if developers avoid it due to a poor UX.
  • No documentation — a platform without TechDocs breeds shadow IT and workarounds.
  • No ownership — the platform needs a dedicated team or at least a responsible platform engineer; otherwise, it degrades over time.
  • Vendor lock-in without a plan — choose open-source tools (Backstage, ArgoCD, Crossplane) or cloud services deliberately, with a clear exit strategy in mind.

Kubernetes as the foundation for an IDP in 2026 is not just a trendy choice — it's a mature technical solution backed by a vast ecosystem. A well-built platform turns the DevOps bottleneck into a scalable enabler for the entire engineering organization.

Technologies

Tags

Ruslan Ismailov

Senior Web / Backend Developer. Senior web/backend developer with 9 years of experience. Stack: PHP, Laravel, PostgreSQL, Redis, Docker, Kubernetes, REST, microservices, CI/CD. More about me →