A platform team creates:
- reusable Terraform modules
- Kubernetes clusters
- deployment pipelines
- workload identities
- observability tooling
- documentation
- service templates
The resources are technically sound. The security controls are thoughtful. The automation works when the platform engineers operate it.
Then an application developer asks:
How do I deploy a new service?
The answer is:
Open a ticket.
Copy this repository.
Change these fourteen files.
Ask the network team for a subnet.
Ask the security team for a role.
Ask the platform team to run Terraform.
Wait for someone to configure the pipeline.
Then message us if the Deployment stays Pending.
The organisation has built internal infrastructure.
It has not necessarily built an internal product.
Calling something a platform does not make it usable. Publishing a Terraform module does not prove that another team can adopt it safely. A portal does not create self-service when every button ends by opening a ticket.
That gives me a practical definition:
An internal platform behaves like a product when its intended users can discover, understand and use supported capabilities to achieve valuable outcomes without needing to understand every underlying implementation detail.
The platform still contains networks, clusters, policies and controllers. The product is the experience through which developers consume those capabilities.
The product test
When someone says:
We have an internal developer platform.
I now want to test that claim.
Discovery
Can the intended user find the capability without asking its author?
Comprehension
Can they understand what it provides, what it requires and what it does not support?
First success
Can they complete the ordinary journey through the documented interface?
Safety
Does the default path satisfy security and operational requirements?
Feedback
Can they see progress, failure and corrective action?
Independence
Can they succeed without a platform engineer performing hidden manual work?
Reliability
Does the capability continue working when individual components fail?
Evolution
Can the platform change without silently breaking consumers?
Support
Is ownership clear when the supported path fails?
Value
Does the platform reduce time, risk or cognitive load?
A platform passes the product test when it improves the user’s outcome, not merely when its resources exist.
The rest of the design should make those claims testable.
Infrastructure resources are not user capabilities
Infrastructure provides technical resources:
network
compute
storage
identity
databases
message brokers
A platform turns selected resources into supported capabilities:
deploy a service
publish an event
connect to a database
obtain workload identity
expose an internal API
observe service health
The distinction matters because developers usually begin with an outcome.
They do not begin with:
I would like one subnet,
three IAM policies,
a load balancer,
some DNS records
and a tasteful collection of security groups.
They begin with:
I need to run the catalog service securely
in the development environment.
A platform should translate that intent into the required infrastructure decisions.
DEVELOPER INTENT
run a private gRPC service
│
▼
PLATFORM CAPABILITY
│
├── workload template
├── identity
├── deployment pipeline
├── network policy
├── observability
└── operational defaults
The underlying infrastructure remains real, but the platform creates a coherent route through it.
For bfstore, that route might begin when a developer wants to create the review service. The developer does not need a Namespace as an end in itself. They need an operable service that can store reviews, consume order.completed, publish review.submitted, expose a private gRPC API and produce useful telemetry.
The platform capability should represent that outcome.
Product thinking begins with a user problem
A reusable module often begins with an infrastructure capability.
A platform product should begin with a user problem.
Application developers, service owners, security engineers and incident responders may need different interfaces, but each should have a clear route to the outcomes they own.
Product thinking asks:
Who is trying to accomplish what?
Which difficulty repeats?
Which decisions can the platform resolve safely?
Which responsibilities must remain with the user?
Without those questions, a platform team can automate its own preferred workflow rather than solve the user’s actual problem.
Suppose the team believes developers need:
a self-service Kubernetes Namespace
It creates a portal button:
Create Namespace
Developers still struggle to deploy services because they also need workload identity, image-pull access, resource defaults, network policy, deployment configuration, service registration, telemetry and CI/CD integration.
The platform delivered the resource mentioned in tickets. It did not deliver the capability behind the request.
A better starting point is a product hypothesis:
We believe that providing a standard
service-deployment capability will reduce
the time and specialist knowledge required
to launch a new internal service.
That hypothesis needs evidence:
- Can an intended user complete the journey?
- How long does adoption take?
- Where do they become blocked?
- Which manual interventions remain?
- Does the resulting service satisfy security and operational expectations?
A platform product is proven through successful use, not its architecture diagram.
The platform interface is an API
Developers may interact with a platform through:
- a command-line tool
- Git pull requests
- Terraform modules
- Kubernetes custom resources
- a developer portal
- service templates
- documented workflows
- ordinary APIs
Each is an interface.
For the bfstore review service, a developer-facing declaration might look like this:
apiVersion: platform.bfstore.dev/v1alpha1
kind: Application
metadata:
name: review
spec:
runtime: go
protocol: grpc
exposure: internal
database:
type: mysql
messaging:
consumes:
- order.completed
publishes:
- review.submitted
The platform could translate that declaration into:
- a deployment workflow
- Kubernetes resources
- workload identity
- database access
- network policy
- Kafka permissions
- dashboards and alerts
- service registration
The object is an API whether or not it travels through HTTP. Its fields form a contract, its defaults express product decisions, its validation explains what the platform supports and its status shows what happened.
SPECIFICATION
what the developer requested
STATUS
what the platform observed,
created and completed
A platform interface deserves the same care as an external API.
A useful abstraction hides work, not consequences
A platform should reduce the amount of specialist knowledge required for ordinary tasks. It should not hide meaningful operational behaviour.
For a production MySQL database, the platform may hide:
- subnet-group creation
- encryption configuration
- security-group rules
- backup scheduling
- credential integration
- monitoring setup
The developer should still understand:
- which service owns the database
- whether it is highly available
- which recovery objective applies
- which changes require replacement
- how schema migrations are performed
- who pays for it
- how deletion is protected
A useful abstraction hides provider-specific construction, repetitive configuration and routine integration. It exposes ownership, cost, lifecycle, recovery behaviour and user responsibilities.
An abstraction that hides consequences creates convenience today and archaeology during the incident.
Opinionated self-service needs guardrails and escape routes
A platform cannot reduce cognitive load while asking developers to make every possible infrastructure decision.
A service capability might choose a supported deployment pattern, health checks, workload identity, structured logging, OpenTelemetry, resource defaults, secure networking and immutable image references.
The developer supplies the service-specific information: name, runtime, owners, dependencies, resource profile, exposure and data requirements. The platform supplies the standard implementation.
DEVELOPER DECISIONS
│
▼
PLATFORM OPINIONS
│
▼
PRODUCTION-READY CAPABILITY
This is the paved road: the common safe route should be easy.
Self-service does not mean unrestricted cloud access. It means allowing users to perform approved actions within safe boundaries.
A developer may request a development database using approved sizes, encryption, backups and a defined cost limit. They may not create an internet-facing production database without encryption or deletion protection.
USER REQUEST
│
▼
SCHEMA VALIDATION
│
▼
POLICY EVALUATION
│
├── permitted ──► reconcile
└── rejected ───► explain why
The rejection experience matters.
AccessDenied
is not a useful product response.
A stronger response is:
Production databases must enable deletion protection.
Set:
database.deletionProtection: true
See:
platform/database-recovery
The policy still blocks the unsafe request, but the interface helps the user reach valid state.
Not every workload will fit the standard path. Unusual networking, specialised hardware, strict latency or data-residency requirements need a deliberate exception model:
- supported through configuration
- supported through an extension point
- supported through a separate capability
- temporarily handled outside the platform
- not supported
The answer should not always be another Boolean variable. Unlimited flexibility eventually exposes every underlying decision again.
A useful escape route documents the reason, ownership, security review, operational support and any plan for future reintegration. The paved road needs exits, but it does not need to become every road.
Documentation and status are part of the interface
Documentation is not a decorative layer added after the platform is complete. It is part of the product.
Useful documentation should answer:
What capability does this provide?
Who should use it?
What is the smallest working example?
Which decisions do I need to make?
Which defaults will the platform apply?
How do I know when it is ready?
What does it cost?
What can fail?
How do I get support?
How do I remove it safely?
The documentation should follow the user’s job, not the repository’s folder structure.
For the bfstore review service, the documented journey might be:
- Create the service from an approved template.
- Declare ownership, runtime and dependencies.
- Add the Protobuf contract.
- Open a pull request.
- Observe platform reconciliation.
- Verify deployment, connectivity and telemetry.
The same interface should expose progress.
Application:
review
Desired state:
submitted
Identity:
ready
Database:
ready
Deployment:
healthy
Messaging:
configured
Telemetry:
receiving data
A useful status model answers:
- What is being created?
- Which stage is active?
- What failed?
- Is the failure retryable?
- What action is required?
- Which revision is being reconciled?
Without status, self-service becomes self-guessing.
Templates need an evolution path
A service template can generate:
- repository structure
- CI workflow
- Dockerfile
- Kubernetes declaration
- health endpoint
- OpenTelemetry setup
- ownership metadata
- documentation skeleton
That accelerates creation, but scaffolding is only a beginning.
A generated service begins to drift as soon as the template evolves.
TEMPLATE v1
creates service A
TEMPLATE v2
creates service B
SERVICE A
does not automatically gain
the new improvements
Long-lived products need maintenance paths through maintained libraries, reusable workflows, centrally reconciled resources, versioned modules or automated upgrade pull requests. The same discipline applies to every consumer-visible interface.
Breaking changes might include:
- renaming module inputs
- changing template structure
- removing an output
- altering policy defaults
- replacing a custom-resource field
- changing generated resource names
The platform should distinguish implementation changes from contract changes. New versions may need migration guidance, automated upgrades, deprecation periods and compatibility testing. Internal users are still users.
Reliability and ownership are product features
An internal platform sits in the delivery and runtime path of other teams.
When it fails, those teams may be unable to deploy, scale, obtain credentials, create environments, observe services or recover incidents.
The platform therefore needs its own reliability model: which capabilities are critical, which dependencies can block delivery, how state is backed up, whether reconciliation resumes after failure and whether users can still operate when the portal is unavailable.
A glossy portal backed by one fragile controller is not a dependable product.
Platform reliability includes graceful degradation. A portal outage should not necessarily stop existing GitOps reconciliation. The interface and the control mechanism do not have to share one failure mode.
Ownership must also be explicit:
- who maintains each capability
- which support hours apply
- how users escalate failures
- who owns deprecation and migration
- where platform responsibility ends
- who approves and supports exceptions
A product is a maintained promise, not merely a released component.
The platform team should not become the control path
A platform team can accidentally become a central queue for infrastructure work.
APPLICATION TEAM
│
▼
PLATFORM TICKET
│
▼
PLATFORM ENGINEER
│
▼
MANUAL TERRAFORM APPLY
This may centralise ownership, but it does not create scalable self-service. The platform team becomes a human API.
A product-oriented path looks different:
APPLICATION TEAM
│
▼
DECLARED REQUEST
│
▼
VALIDATION AND POLICY
│
▼
AUTOMATED RECONCILIATION
│
▼
STATUS AND FEEDBACK
Platform engineers still operate the control plane, evolve capabilities and support genuine exceptions. They do not manually translate every ordinary request.
A developer portal can provide discovery, documentation, templates, ownership data and operational links, but it is not the platform.
PORTAL
discovery and interaction
PLATFORM
APIs, modules, controllers,
policies, pipelines and runtime services
A portal button that opens a ticket is a polished queue. For bfstore, Backstage might organise ownership, API contracts, deployment status, dashboards and templates while Crossplane, Terraform, GitOps controllers and cloud APIs perform the work.
The portal organises the experience; it does not replace the control plane.
Test the capability at several layers
A platform capability should be tested as more than infrastructure code.
Configuration tests
Does the module or controller produce the intended resources?
Policy tests
Are encryption, identity, network and lifecycle guardrails enforced?
Integration tests
Can the capability be created in a real environment?
Contract tests
Do inputs, outputs and status remain compatible?
Journey tests
Can an intended user complete the whole task?
For the bfstore review service, a journey test begins with:
Create a new internal gRPC service
that stores reviews and exchanges
approved business events.
It ends when automation confirms that:
- the service is deployed and healthy
- it can connect to its database
- it can consume
order.completed - it can publish
review.submitted - it cannot consume unrelated topics
- an approved caller can reach its gRPC API
- an unapproved Namespace cannot
- logs, metrics and traces appear
- the running image matches the declared digest
The test should not require a platform engineer to repair hidden steps halfway through.
That is where infrastructure testing becomes product testing.
The platform product is not the YAML object.
It is the reliable journey from service intent to an operable service.
Measure outcomes and learn from friction
A platform dashboard might report:
Namespaces created:
83
Terraform modules downloaded:
214
Portal visits:
1,602
Those numbers show activity, not improved delivery.
A stronger measurement model connects use to outcomes:
| Dimension | Example measure |
|---|---|
| Speed | Median time from repository creation to healthy development deployment |
| Independence | Percentage of standard journeys completed without platform-team intervention |
| Reliability | Successful reconciliation rate and recovery time |
| Safety | Percentage of deployments meeting policy on first submission |
| Adoption | Percentage of eligible workloads using the supported path |
| Experience | Repeated support requests per completed journey |
The strongest goals combine speed and quality:
Reduce median first-deployment time
from two days to under one hour
while maintaining
policy compliance,
platform reliability
and clear operational ownership.
Support requests provide further evidence.
Repeated questions such as:
Which role should my service use?
Why can my Pod not pull its image?
Where do I find the trace?
How do I request a database?
Why did policy reject my deployment?
may reveal unclear documentation, missing automation, poor error messages, weak defaults or an unsupported capability.
The platform team can answer every message individually or treat the pattern as product research. Mandatory use may create compliance while hiding poor experience and shadow automation.
A platform earns adoption through clear value, lower cognitive load, faster delivery, safer defaults, reliable operation and useful support.
Standards still matter. The platform product should make approved identity, security and observability controls easier to satisfy than to bypass.
The mental model I am keeping
My earlier model was:
PLATFORM ENGINEERING
build shared infrastructure
publish modules
provide a portal
The stronger model is:
USER PROBLEM
│
▼
PRODUCT HYPOTHESIS
│
▼
SUPPORTED CAPABILITY
│
┌───────────────┼───────────────┐
│ │ │
▼ ▼ ▼
INTERFACE GUARDRAILS DEFAULTS
│ │ │
└───────────────┼───────────────┘
▼
AUTOMATED CONTROL PLANE
│
▼
VISIBLE STATUS
│
▼
USER OUTCOME
│
▼
FEEDBACK AND CHANGE
Infrastructure answers which technical resources exist. A platform capability answers which useful outcome a user can request. The interface explains how to express that need. Defaults and guardrails decide what the platform will handle and prevent. Reconciliation and status create, maintain and explain the result. Documentation and support help users succeed without private knowledge. Metrics test whether the outcome improved. Ownership determines who maintains the promise after adoption.
Internal infrastructure becomes a platform product when it turns specialist decisions into dependable capabilities that teams can discover, request and operate safely.
The decisive test is not whether the platform team has built modules, controllers or a portal.
It is whether an intended user can follow the supported path to a secure, observable and operable outcome, understand the result and recover from problems without hidden manual intervention.
Everything else is evidence, implementation or unfinished product work.