Production-ready guide covering internal developer platform build vs buy decision with implementation patterns, code examples, and anti-patterns for enterprise engineering teams.
An internal developer platform (IDP) enables teams to build, deploy, and operate software consistently and efficiently. The build vs buy decision determines whether an organisation develops its platform in-house or adopts an external solution. This decision matters when teams face repeated friction in onboarding, releasing, or managing services, and when the cost of customisation and integration begins to outweigh the benefits of using a pre-built system.
Before choosing between building and buying, define what the platform must do. Start with the core workflows: provisioning environments, deploying services, monitoring health, and managing configuration. These workflows are expressed through a sequence of actions, each of which must be executable, observable, and repeatable.
Provisioning a new environment requires a request that triggers a series of infrastructure and service setup steps. The request is typically a POST to /api/environments with a JSON body:
{
"name": "staging-2024-03-15",
"service": "orders",
"region": "us-east-1",
"tags": {
"team": "payments",
"owner": "[email protected]"
}
}
The platform responds with a status and a unique environment ID. The system may return:
{
"id": "env-9a3b4c",
"status": "provisioning",
"created_at": "2024-03-15T10:23:01Z",
"resources": [
{
"type": "aws:ec2:instance",
"id": "i-01a2b3c4d5e6f",
"status": "pending"
},
{
"type": "kubernetes:namespace",
"name": "orders-staging",
"status": "creating"
}
]
}
The status field is updated as each underlying system completes its work. A GET /api/environments/env-9a3b4c shows the evolving state. The platform must expose these endpoints and maintain a consistent state model across all components.
Deployment workflows involve pushing code to a repository and observing the resulting build, test, and release steps. The platform listens for git push events or accepts a POST /api/deployments with a commit_hash, branch, and target_environment. A deployment may require approval, which is tracked via a PATCH /api/deployments/{id}:
{
"status": "pending-approval",
"approval_required": true,
"approver": "[email protected]",
"review_comments": [
"Consider increasing memory limits.",
"Add e2e tests for checkout flow."
]
}
The platform updates the deployment status when the approver responds. The system must handle retries, timeouts, and rollbacks. A failed deployment returns an error field with a machine-readable code and human-readable message:
{
"status": "failed",
"error": {
"code": "DEPLOYMENT_TIMEOUT",
"message": "Build took longer than 15 minutes",
"details": {
"duration_seconds": 1624,
"build_log": "https://ci.acme.com/logs/5a3b4c"
}
}
}
Monitoring is driven by health checks and alerts. A service registers itself via a PUT /api/services with a health_check_url and metrics_endpoint. The platform periodically polls health_check_url and expects a 200 OK response with a JSON body:
{
"status": "healthy",
"timestamp": "2024-03-15T10:30:00Z",
"details": {
"database": {
"connected": true,
"latency_ms": 42
},
"cache": {
"hits": 87,
"misses": 13
}
}
}
If the response is 503 Service Unavailable, the platform marks the service as degraded. Alerts are triggered when the status changes or when a metric exceeds a threshold.
The first step is to map existing workflows to the platform’s capabilities. Use a checklist of essential features:
A team that uses kubectl apply -f for deployments but lacks consistent environment setup is a candidate for a build. A team that relies on Jenkins but has no standard way to define service metadata is missing foundational platform capabilities.
A platform built on top of existing tools can be more cost-effective than a fully custom system. Start by listing the current tools and their responsibilities:
git for source controldocker build and docker push for containerisationkubectl and helm for Kubernetes deploymentsprometheus and grafana for monitoringsentry for error trackingvault for secretsEach tool must be integrated into the platform, but integration is not automatic. The platform must orchestrate these tools through APIs, event triggers, or CLI scripts.
For example, a new deployment event in the platform must trigger docker build, push the image to a registry, and then run kubectl apply -f on a Deployment manifest. This orchestration requires a script or workflow engine.
A common failure is misaligned expectations about what a tool does. helm template produces a manifest, but the platform expects a Deployment object. The platform must validate that the rendered manifest includes the kind: Deployment field and that the spec.replicas is set. A missing replicas field causes the deployment to fail silently.
The decision to build or buy hinges on how much customisation is required. A buy solution is viable when the platform can handle most workflows with standard configuration, but a build is justified when teams need to extend or adapt the platform to their specific needs.
Configuration is expressed through a declarative format, typically YAML. A service’s platform.yaml file might define its default environment, CI/CD pipeline, and required features:
service:
name: "orders"
type: "microservice"
environment:
- name: "production"
variables:
DATABASE_URL: "postgresql://prod.acme.com/orders"
LOG_LEVEL: "info"
secrets:
- "db-password"
- "payment-api-key"
pipeline:
branches:
main:
stages:
- name: "build"
command: "make build"
timeout: "10m"
- name: "test"
command: "make test"
environment:
TEST_DB: "test.acme.com"
- name: "deploy"
command: "make deploy"
target: "production"
features:
- "feature-flags"
- "tracing"
- "auto-scaling"
When the platform reads this file, it must validate the structure and resolve dependencies. A missing pipeline.stages field is a common error. The system logs:
ERROR: platform: invalid configuration for service orders
path: pipeline.stages
expected: array of objects
found: null
This failure is silent unless the platform explicitly checks for required fields. A build platform can define custom features that accept additional configuration keys. A feature-flags block may include default_strategy and rollout_percentage, which a buy platform might not expose.
Integration complexity is the hidden cost of buy vs build. A buy platform may offer a standard API, but the real work lies in connecting it to existing systems.
A GET /api/services returns a list of services with metadata. The platform must populate this list from multiple sources: git repositories, helm charts, and docker registries. The integration must handle:
git repositories for platform.yaml filesplatform.yaml for a given commit_hashThe platform must expose a GET /api/services?updated_since=2024-03-15T00:00:00Z endpoint that returns only services updated after a timestamp. The response includes a next_page_token for pagination.
The integration fails silently when a service is added to git but the platform.yaml file is missing or malformed. The platform logs:
WARNING: platform: failed to load service from git
repository: https://git.acme.com/orders
branch: main
path: platform.yaml
error: "file not found or invalid YAML"
The system continues to serve the service list, but new services appear only after a manual refresh or a cron job.
A built platform evolves over time. Teams contribute to the platform’s codebase, define new features, and fix bugs. The platform’s own build process must be versioned and tested. A platform-ci pipeline runs on platform.yaml files in the platform repository.
The platform-ci pipeline includes:
npm install and npm run builddocker build and docker push for the platform’s web UIkubectl apply -f for deploying the platform to Kubernetesmake test with integration tests that simulate real workflowsThe pipeline fails when a new feature is added but the platform.yaml file is not updated. The system logs:
ERROR: platform-ci: failed to build platform
step: npm run build
error: "Failed to resolve dependency: @acme/[email protected]"
details: "package.json does not list @acme/feature-flags as a dependency"
A buy platform, by contrast, relies on updates from a vendor or internal team. The team must decide how frequently to upgrade and what to do when a new release breaks existing workflows.
The upgrade process involves:
platform upgrade --from 2.3.1 --to 2.4.0The migration fails when a required column is missing in the services table. The system logs:
ERROR: platform: migration failed
version: 2.4.0
step: add-service-environment-column
error: "column environment_id does not exist in services table"
details: "expected column environment_id of type UUID, but found only id, name, created_at"
This failure is silent unless the migration script includes a SELECT 1 FROM information_schema.columns WHERE table_name = 'services' AND column_name = 'environment_id'.
The build vs buy decision is not a one-time event. It is a continuous evaluation of the platform’s fit with the team’s needs. The organisation must define a set of signals that indicate when the platform is working well.
Use a GET /api/health endpoint that returns:
{
"status": "ok",
"checks": [
{
"name": "environment-provisioning",
"status": "healthy",
"last_updated": "2024-03-15T10:25:00Z"
},
{
"name": "deployment-pipeline",
"status": "degraded",
"last_updated": "2024-03-15T10:24:30Z",
"details": {
"failed_runs": 3,
"average_duration_seconds": 420
}
}
]
}
The platform’s health is monitored by a dashboard that shows the status of each check. A degraded deployment-pipeline check triggers an alert and a Slack message.
When the platform becomes a bottleneck—environments take over an hour to provision, deployments fail in 20% of cases—then building the platform is justified. When the platform is stable, reusable, and extensible, buying a solution is viable.
The decision is final when the team can articulate the platform’s core values: consistency, speed, and reliability. The platform is not a collection of tools. It is a shared system that every developer trusts to deliver their work.
This page was rewritten on 18 October 2026. It replaced a templated version that was not specific to its own title. How these pages are written and checked. If anything here is wrong, tell us at [email protected] and we will correct it.
We use cookies for analytics (Google Analytics) and advertising (Google AdSense) to improve your experience and support free content. Privacy Policy