Treat internal platforms as products with users, roadmaps, and feedback loops. Covers platform product management, developer experience metrics, adoption strategies, golden paths, and the patterns that turn internal platforms from mandated tools into beloved products.
Platform as a Product is the discipline of treating a shared engineering platform as a first-class software product: it has a defined roadmap, a user base, a support model, a SLA, and a feedback loop. It matters when teams across an organisation rely on common services—CI/CD, deployment, monitoring, infrastructure provisioning, and developer tooling—to build and ship features faster, with higher consistency and fewer operational surprises. This is not just a technical stack; it is a product that evolves to meet the needs of its internal customers: developers, site reliability engineers, and product managers.
When a new service team joins the platform, their onboarding process must be predictable, repeatable, and self-serve. The platform exposes a standard way to register a service, define its lifecycle, and integrate with shared tooling.
The process starts with git-based service registration:
git clone https://platform.example.com/registry.git
cd registry
mkdir -p services/my-new-service
echo '{
"name": "my-new-service",
"owner": "[email protected]",
"description": "API for user profile management",
"team": "users",
"runtime": "nodejs",
"default-branch": "main",
"ci": "github-actions",
"monitoring": true,
"tracing": true,
"logging": true
}' > services/my-new-service/manifest.json
git add services/my-new-service
git commit -m "Add my-new-service to platform registry"
git push origin main
The platform automatically detects the new service and triggers a pipeline. The first ci step runs make setup in the service repository, which must be present and executable.
setup:
@echo "Setting up environment..."
@npm install
@mkdir -p build
@echo "Setup complete"
The platform’s CI engine checks for make setup and runs it. If the Makefile is missing or the setup target fails, the platform logs the error in the event stream:
ERROR: make setup failed in my-new-service
Exit code: 1
Output:
npm: command not found
This indicates that node is not available in the CI worker’s environment. The platform resolves this by providing a ci-worker.yaml file in the service root:
# ci-worker.yaml
image: node:18-alpine
cache:
- npm
- .next
steps:
- run: npm install
- run: npm run build
- run: npm test
The platform consumes this file and uses the specified image and cache configuration. If ci-worker.yaml is missing, the platform applies a default configuration based on the runtime field in manifest.json.
Once a service is on the platform, its lifecycle is managed through declarative configuration and automated workflows.
To deploy a new version, the service team pushes a git tag:
git tag v1.2.3
git push origin v1.2.3
The platform detects the tag and triggers a deploy workflow. The workflow reads deploy.yaml in the service root:
# deploy.yaml
target: production
strategy: rolling
rollout:
interval: 30s
max-in-flight: 2
health-check:
path: /health
timeout: 10s
interval: 5s
max-failures: 3
pre-deploy:
- run: kubectl apply -f k8s/manifests/
- run: echo "Pre-deploy hooks completed"
post-deploy:
- run: curl -X POST https://monitoring.example.com/health
- run: echo "Post-deploy hooks completed"
The platform executes the workflow in order. If any step fails, the rollout stops and sends a notification to the service team via email and Slack.
If health-check.interval is not set, the platform falls back to a default value of 10s. If strategy is blue-green but blue-green is not a recognized strategy, the platform logs:
WARNING: Unknown deployment strategy 'blue-green'. Using 'rolling' as fallback.
The platform also supports a rollout.yaml file for more complex scenarios. When deploy.yaml and rollout.yaml both exist, the platform merges them, with deploy.yaml taking precedence.
Promotion is handled via a promotion.yaml file:
# promotion.yaml
stages:
- name: staging
target: staging
approval:
required: true
reviewers: [ "dev-lead", "platform-engineer" ]
rollout:
interval: 60s
max-in-flight: 3
- name: production
target: production
approval:
required: true
reviewers: [ "platform-engineer", "cto" ]
rollout:
interval: 15s
max-in-flight: 1
When a service team pushes a version to staging, the platform creates a promotion request. The request includes:
The platform polls https://api.platform.example.com/v1/services/{name}/promotion-status every 30 seconds. The response is a JSON object:
{
"service": "my-new-service",
"current-stage": "staging",
"next-stage": "production",
"status": "waiting-for-approval",
"approval-required": true,
"reviewers": [
{
"user": "dev-lead",
"status": "pending",
"timestamp": "2024-04-05T10:00:00Z"
},
{
"user": "platform-engineer",
"status": "approved",
"timestamp": "2024-04-05T10:05:00Z"
}
],
"deployment": {
"id": "dpl-7a3f2b",
"target": "staging",
"status": "completed",
"started": "2024-04-05T10:00:00Z",
"finished": "2024-04-05T10:15:00Z"
}
}
When all reviewers approve, the platform transitions the service to the next stage. If a reviewer updates their status, the platform updates the approval object and sends a real-time update via WebSocket.
The platform provides built-in observability for all services. Each service must expose a /health endpoint and include a observability.yaml file.
# observability.yaml
tracing:
enabled: true
exporter:
type: jaeger
endpoint: http://jaeger-collector:14268/api/traces
sampling:
rate: 0.1
max-connections: 100
logging:
level: info
format: json
output: stdout
retention: 7d
monitoring:
alerts:
- name: "High request latency"
query: "avg(rate(http_request_duration_seconds[5m])) > 0.5"
severity: warning
duration: 5m
slack: true
metrics:
- name: "http_requests_total"
labels: [ "method", "status" ]
- name: "memory_usage_bytes"
labels: [ "instance" ]
The platform collects logs, metrics, and traces and serves them through a unified dashboard. If tracing.exporter.endpoint is unreachable, the platform logs:
ERROR: Tracing exporter failed to connect to http://jaeger-collector:14268/api/traces
Status: 503 Service Unavailable
Response body: {"error": "Service not available"}
The platform retries for 5 minutes, then marks the service as degraded. If the endpoint remains unreachable for 10 minutes, it triggers a service-degraded alert.
When an alert fires, the platform creates an incident in the incidents table of the platform database:
INSERT INTO incidents (
service,
title,
severity,
status,
created_at,
last_updated
) VALUES (
'my-new-service',
'High request latency in production',
'warning',
'open',
'2024-04-05T10:20:00Z',
'2024-04-05T10:20:00Z'
);
The incident includes a link to the service’s dashboard, a summary of the alert, and a timeline of recent events.
The platform collects feedback from developers through a feedback.yaml file and a structured API.
# feedback.yaml
categories:
- name: "Onboarding"
description: "Experience when joining the platform"
priority: high
questions:
- question: "How easy was it to register your service?"
type: scale
min: 1
max: 5
default: 3
- question: "What was missing or confusing?"
type: text
required: true
- name: "Deployment"
description: "Feedback on deployment process"
priority: medium
questions:
- question: "How long did it take to deploy a new version?"
type: time
unit: minutes
- question: "What would improve the deployment workflow?"
type: text
The platform surfaces a feedback survey via a GET /api/feedback/for-service/{service} endpoint. The response is a JSON object with id, service, survey, and questions.
When a developer submits feedback, the platform stores it in a feedback_submissions table:
CREATE TABLE feedback_submissions (
id UUID PRIMARY KEY,
service TEXT NOT NULL,
survey_id UUID NOT NULL,
submitted_at TIMESTAMP WITH TIME ZONE,
answers JSONB
);
The platform uses answers as a JSONB column. It supports queries like:
SELECT service, AVG(answers->'onboarding'->'how-easy') AS avg_rating
FROM feedback_submissions
WHERE submitted_at > '2024-04-01T00:00:00Z'
GROUP BY service;
The platform also supports feedback.yaml being updated dynamically. If the file changes after a submission, the platform updates the survey and marks all existing submissions as outdated.
The following are common points of failure that go unnoticed unless monitored:
manifest.json: If a service does not include manifest.json, the platform uses default values for runtime, ci, and monitoring, but logs a WARNING: manifest.json not found in service root.deploy.yaml schema: If rollout.interval is specified as a string instead of a duration (e.g., "30" instead of "30s"), the platform parses it as 30s but logs: WARNING: Invalid rollout interval format in deploy.yaml: expected duration (e.g., 30s, 2m), got 30
registry.git repository, the platform logs: ERROR: Service my-new-service not found in platform registry
Suggestion: Run `git add services/my-new-service && git commit -m "Register service" && git push`
/health takes longer than 10s to respond, the platform marks the service as unhealthy and logs: CRITICAL: Service my-new-service: /health response took 12.3s, exceeding threshold of 10s
ci-worker.yaml: When ci-worker.yaml is present but missing the image key, the platform logs: WARNING: CI worker image not specified in ci-worker.yaml. Using default image: ubuntu:22.04.
jaeger-collector is unreachable but no error is logged, the platform assumes the exporter is working, but traces are missing. The platform adds a tracing-collector-check job that runs every 5 minutes and logs when the collector is down.Platform as a Product is defined by a few key patterns:
git add, git commit, git push, with no manual steps.The platform is not a collection of tools; it is a product that evolves with its users. Every change to a manifest.json, every deploy.yaml, every feedback.yaml, is a product decision. The platform is not just a stack—it is a contract between engineering teams, and it is maintained as a product in its own right.
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