Building a Developer Platform on Kubernetes: An Internal PaaS for Engineering Teams
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:
- The CI/CD pipeline builds a Docker image and pushes it to the registry tagged as
pr-{number}. - The pipeline creates a Kubernetes namespace
preview-142. - ArgoCD creates an Application using the Helm chart, overriding the image tag and hostname.
- cert-manager issues a TLS certificate for the preview domain.
- 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)
- Deploy a production-ready Kubernetes cluster (EKS/GKE or kubeadm on bare metal).
- Set up a unified Docker Registry (Harbor).
- Deploy ArgoCD and migrate the first 2–3 services to GitOps.
- Create a base Helm chart for microservices.
Phase 2: CI/CD Standardization (Months 2–3)
- Create shared CI/CD templates for Go and PHP services.
- Introduce the External Secrets Operator + Vault.
- Configure cert-manager for automatic TLS certificates.
- Launch the first preview environments.
Phase 3: Developer Portal (Months 3–5)
- Deploy Backstage and populate the Software Catalog.
- Create Software Templates for common service types (Go API, PHP/Laravel Worker).
- Connect the Kubernetes and CI/CD plugins to Backstage.
- Set up TechDocs for internal documentation.
Phase 4: Self-Service Infrastructure (Months 5–7)
- Introduce Crossplane for self-service databases and queues.
- Set up DORA metrics dashboards in Grafana.
- Run the first quarterly DX survey.
- 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 →