Orchestration

Streamline Platform Workload: Score Your Specs

July 3, 2026 Kubezilla Team 16 min read

Platform teams often grapple with the complexities of standardizing workload definitions across diverse development teams. Developers, on the other hand, frequently face a steep learning curve when translating their application requirements into Kubernetes-native YAML, leading to inconsistencies, misconfigurations, and deployment headaches. This chasm between application-centric development and infrastructure-centric operations creates friction, slows down deployments, and increases the operational burden.

Enter Score, an open-source workload specification that aims to bridge this gap. Score provides a simplified, environment-agnostic, and developer-friendly way to describe an application’s requirements, independent of the underlying infrastructure. By focusing on what an application needs rather than how it’s deployed, Score empowers developers to define their workloads once, and then enables platform teams to translate these definitions into various platform-specific configurations, including Kubernetes. This approach fosters consistency, reduces cognitive load, and accelerates the path to production.

This comprehensive guide will walk you through the Score specification, demonstrating how it can streamline your Kubernetes deployments. We’ll explore its core concepts, show you how to write Score files, and illustrate how to use the Score-Helm transformer to generate Kubernetes manifests. By the end, you’ll understand how Score can revolutionize your platform engineering efforts, making life easier for both developers and operations teams.

TL;DR: Score Workload Specification

Score simplifies Kubernetes deployments by providing an environment-agnostic workload specification. Define your application’s needs once, then use transformers to generate platform-specific configurations.

Key Commands:

  • Install Score CLI:
    brew install score-spec/score/score-cli # macOS
    # Or download from GitHub releases: https://github.com/score-spec/score-cli/releases
  • Install Score-Helm Transformer:
    brew install score-spec/score/score-helm # macOS
    # Or download from GitHub releases: https://github.com/score-spec/score-helm/releases
  • Generate Helm Values from Score:
    score-helm run -f score.yaml -o values.yaml
  • Generate Kubernetes Manifests from Helm:
    helm template my-app . -f values.yaml

Prerequisites

Before we dive deep into Score, ensure you have the following tools and knowledge:

  • Kubernetes Cluster: Access to a functional Kubernetes cluster (e.g., Minikube, kind, or a cloud-managed cluster).
  • kubectl: The Kubernetes command-line tool, configured to connect to your cluster. You can find installation instructions on the official Kubernetes documentation.
  • Helm: The Kubernetes package manager. Install it by following the instructions on the Helm website.
  • Score CLI: The command-line interface for validating and working with Score files.
  • Score-Helm Transformer: A tool that translates Score files into Helm values files.
  • Basic YAML Knowledge: Familiarity with YAML syntax is essential.
  • Basic Kubernetes Concepts: Understanding of Pods, Deployments, Services, and Ingress is beneficial.

Installation of Score CLI and Score-Helm:

For macOS users, Homebrew is the easiest way:

brew install score-spec/score/score-cli
brew install score-spec/score/score-helm

For other operating systems, you can download the binaries directly from their respective GitHub release pages:

Step-by-Step Guide: Leveraging Score for Kubernetes Workloads

Step 1: Understanding the Score Workload Specification

The Score specification is a YAML-based schema designed to describe a workload’s requirements in a platform-agnostic manner. It focuses on declaring resources, environment variables, containers, and services without dictating the underlying infrastructure. This abstraction allows developers to define their application’s needs once, and then platform teams can use various “transformers” to generate platform-specific configurations (e.g., Kubernetes YAML, AWS CloudFormation, Azure ARM templates).

A Score file typically includes sections for apiVersion, metadata, containers, resources, and service. The containers section defines the application’s containers, their images, ports, and environment variables. The resources section declares external dependencies like databases, message queues, or persistent storage, allowing the platform to provision or connect to these services. The service section defines how the application should be exposed to other services or the outside world, similar to a Kubernetes Service. This separation of concerns is a core tenet of platform engineering, enabling a clear contract between developers and the platform.

# score.yaml
apiVersion: score.dev/v1b1
metadata:
  name: my-web-app
  description: A simple Node.js web application
containers:
  my-app:
    image: node:18-alpine
    command: ["node", "server.js"]
    ports:
      www:
        port: 8080
        targetPort: 8080
    variables:
      NODE_ENV: production
      PORT: "8080"
      DATABASE_URL: ${resources.db.connectionString}
resources:
  db:
    type: postgresql
    class: standard
  cache:
    type: redis
    class: basic
  storage:
    type: volume
    class: fast-ssd
    mount: /data
service:
  ports:
    http:
      port: 80
      targetPort: 8080
      protocol: TCP

Verify:
While there’s no direct output to verify here, understanding the structure is key. You can use the Score CLI to validate the syntax of your Score file:

score validate -f score.yaml

Expected Output (if valid):

./score.yaml is a valid Score file.

Step 2: Defining a Basic Workload with Score

Let’s create a more concrete example: a simple “Hello World” Node.js application. We’ll define its container, expose a port, and specify an environment variable. Notice how we refrain from mentioning Kubernetes-specific details like Deployment strategies, Service types (ClusterIP, NodePort, LoadBalancer), or Ingress rules. This is the power of Score – it abstracts away the infrastructure boilerplate.

The metadata.name field is crucial as it typically becomes the base name for generated resources. Under containers, we define a single container named my-app, specifying its Docker image and the command to run. The ports section within the container defines the internal application port, while the top-level service section defines how this port should be exposed externally. The variables section allows you to define environment variables, including dynamic ones using the ${resources..} syntax, which will be resolved by the transformer.

# score.yaml
apiVersion: score.dev/v1b1
metadata:
  name: hello-score
  description: A simple "Hello World" application defined with Score.
containers:
  hello-app:
    image: nginxdemos/hello:plain-text
    ports:
      web:
        port: 80
        targetPort: 80
    variables:
      MESSAGE: "Hello from Score!"
service:
  ports:
    http:
      port: 80
      targetPort: 80
      protocol: TCP

Verify:
Validate the Score file to ensure correctness.

score validate -f score.yaml

Expected Output:

./score.yaml is a valid Score file.

Step 3: Generating Helm Values with Score-Helm

Now that we have our Score file, we need to transform it into something Kubernetes understands. The score-helm transformer is designed precisely for this. It takes a Score file and converts it into a Helm values file (values.yaml) that can then be used with a generic Helm chart. This approach leverages the power of Helm for templating while keeping the application definition clean and platform-agnostic.

The score-helm command processes the Score file and applies a default set of transformation rules to produce a structured values.yaml. This file typically contains common Helm values like image names, replica counts, service ports, and ingress configurations. Platform teams can customize the Helm chart to interpret these values and generate the desired Kubernetes resources. This separation allows platform teams to maintain complex, secure, and opinionated Helm charts, while developers only interact with the simpler Score specification. For advanced networking configurations, platform teams might integrate with tools like Kubernetes Gateway API or Istio Ambient Mesh through their Helm charts.

score-helm run -f score.yaml -o values.yaml

Verify:
Inspect the generated values.yaml file. It should contain Helm-specific configurations derived from your Score file.

cat values.yaml

Expected Output (truncated, actual output may vary based on Score-Helm version):

# values.yaml (generated by score-helm)
# ...
image:
  repository: nginxdemos/hello
  tag: plain-text
  pullPolicy: IfNotPresent
nameOverride: hello-score
fullnameOverride: hello-score

container:
  env:
    MESSAGE: "Hello from Score!"
  ports:
    web:
      port: 80
      targetPort: 80

service:
  type: ClusterIP
  port: 80
  targetPort: 80
  protocol: TCP
# ...

Step 4: Creating a Generic Helm Chart

To deploy our Score-defined workload, we need a generic Helm chart that can consume the values.yaml generated by score-helm. This chart acts as the “platform contract,” defining how the Score specification translates into Kubernetes resources. A good practice is to create a flexible chart that can generate Deployments, Services, and optionally Ingresses based on the provided values.

For this example, we’ll create a very basic Helm chart. In a real-world scenario, your platform team would maintain a more sophisticated chart that handles various infrastructure concerns like Network Policies, resource limits, autoscaling (e.g., with Karpenter for node scaling), and observability integrations (e.g., with eBPF Observability with Hubble).

First, create a new Helm chart directory structure:

helm create hello-score-chart
cd hello-score-chart
rm -rf charts templates/* # Clean up default templates

Now, let’s create a minimal templates/deployment.yaml and templates/service.yaml based on common Helm patterns that expect values like image.repository, image.tag, container.env, and service.port.

hello-score-chart/templates/deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Release.Name }}
  labels:
    app: {{ .Release.Name }}
spec:
  replicas: 1
  selector:
    matchLabels:
      app: {{ .Release.Name }}
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - name: {{ .Release.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          ports:
            - containerPort: {{ .Values.container.ports.web.targetPort }}
          env:
          {{- range $key, $value := .Values.container.env }}
            - name: {{ $key }}
              value: {{ $value | quote }}
          {{- end }}

hello-score-chart/templates/service.yaml:

apiVersion: v1
kind: Service
metadata:
  name: {{ .Release.Name }}
  labels:
    app: {{ .Release.Name }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: {{ .Values.service.protocol }}
      name: http
  selector:
    app: {{ .Release.Name }}

hello-score-chart/Chart.yaml:

apiVersion: v2
name: hello-score-chart
description: A generic Helm chart for Score-defined workloads
type: application
version: 0.1.0
appVersion: "1.0.0"

Verify:
Ensure your chart structure is correct.

ls -l hello-score-chart/templates/

Expected Output:

-rw-r--r--  1 user  group  450 Oct 26 10:00 deployment.yaml
-rw-r--r--  1 user  group  320 Oct 26 10:00 service.yaml

Step 5: Inspecting the Generated Kubernetes Manifests

With the Helm values file and our generic Helm chart, we can now generate the final Kubernetes manifests. This step uses Helm’s templating engine to combine the chart’s templates with the values generated by Score-Helm. This is a crucial step for platform teams to review the actual Kubernetes resources that will be deployed.

Platform teams can use this output to ensure that all necessary security policies (e.g., Pod Security Standards, Kyverno policies), resource quotas, and other operational requirements are met. It’s also an excellent way to debug any discrepancies between the Score definition and the final Kubernetes configuration.

Run helm template from the directory containing your hello-score-chart and specify the values.yaml generated earlier. Make sure your score.yaml and values.yaml are in the parent directory, or adjust paths accordingly.

helm template hello-score ./hello-score-chart -f ../values.yaml

Verify:
Review the output, which should be valid Kubernetes YAML for a Deployment and a Service.

Expected Output (truncated):

---
# Source: hello-score-chart/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-score
  labels:
    app: hello-score
spec:
  replicas: 1
  selector:
    matchLabels:
      app: hello-score
  template:
    metadata:
      labels:
        app: hello-score
    spec:
      containers:
        - name: hello-score
          image: "nginxdemos/hello:plain-text"
          ports:
            - containerPort: 80
          env:
            - name: MESSAGE
              value: "Hello from Score!"
---
# Source: hello-score-chart/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: hello-score
  labels:
    app: hello-score
spec:
  type: ClusterIP
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      name: http
  selector:
    app: hello-score

Step 6: Deploying to Kubernetes

Finally, let’s deploy our Score-defined application to the Kubernetes cluster using Helm. This step demonstrates the end-to-end workflow: Score -> Score-Helm -> Helm -> Kubernetes. This streamlined process allows developers to focus on their application’s logic, while platform teams ensure robust and consistent deployments.

The helm upgrade --install command is idempotent, meaning it will install the release if it doesn’t exist or upgrade it if it does. This makes it suitable for CI/CD pipelines. Ensure you are in the directory where your hello-score-chart is located, and your values.yaml is accessible.

helm upgrade --install hello-score ./hello-score-chart -f ../values.yaml

Verify:
Check the deployment and service status in your Kubernetes cluster.

kubectl get deployments hello-score
kubectl get services hello-score

Expected Output:

NAME          READY   UP-TO-DATE   AVAILABLE   AGE
hello-score   1/1     1            1           XXs

NAME          TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
hello-score   ClusterIP   X.X.X.X       <none>        80/TCP    XXs

You can also port-forward to the service and access the application:

kubectl port-forward service/hello-score 8080:80

Then, in another terminal, access it:

curl localhost:8080

Expected Output:

Hello from Score!

Production Considerations

Deploying Score-defined workloads in production requires more than just basic deployment. Here are critical considerations for platform teams:

  1. Robust Helm Chart: The generic Helm chart maintained by the platform team is the cornerstone. It should be comprehensive, including best practices for:
    • Resource Limits and Requests: CPU and memory limits to prevent resource exhaustion.
    • Probes: Liveness and readiness probes for application health checks.
    • Autoscaling: Horizontal Pod Autoscaler (HPA) configurations. For node autoscaling, consider tools like Karpenter.
    • Security Contexts: RunAsUser, fsGroup, readOnlyRootFilesystem for enhanced security. Integrate with tools like Sigstore and Kyverno for supply chain security and policy enforcement.
    • Network Policies: Default Kubernetes Network Policies to restrict traffic. For advanced CNI features like WireGuard encryption, explore Cilium WireGuard Encryption.
    • Ingress/Gateway: Integration with NGINX Ingress, Kubernetes Gateway API, or service mesh (e.g., Istio Ambient Mesh).
    • Observability: Sidecars for logging agents (Fluent Bit), Prometheus scrape annotations, and integration with distributed tracing. Leverage eBPF Observability with Hubble for deep insights.
    • Secrets Management: Integration with external secret stores (e.g., HashiCorp Vault, AWS Secrets Manager) via CSI drivers or operators.
  2. CI/CD Integration: Automate the Score -> Score-Helm -> Helm deployment process within your CI/CD pipelines. This ensures consistency and reduces manual errors.
  3. Environment-Specific Overrides: Score supports environment-specific configuration via overlays or by passing additional values to score-helm. Platform teams should define how environment-specific values (e.g., database connection strings, API keys) are managed and injected securely.
  4. Resource Provisioning: For resources defined in Score (e.g., postgresql, redis), the platform team needs a mechanism to provision these. This could involve cloud service operators (e.g., AWS RDS Operator), Crossplane, or direct cloud provider APIs.
  5. Version Control: Both Score files and the generic Helm charts should be under strict version control. This allows for rollbacks and clear auditing.
  6. Documentation and Training: Provide clear documentation and training for developers on how to write effective Score files and understand the capabilities of the underlying platform.
  7. GPU Workloads: For applications requiring specialized hardware like GPUs (common in AI/ML), the Helm chart would need to include configurations for GPU resource requests and appropriate device plugins. Refer to guides like Running LLMs on Kubernetes: GPU Scheduling Best Practices for deep insights.

Troubleshooting

Here are common issues you might encounter when working with Score and their solutions:

  1. Issue: score validate fails with schema errors.

    Solution: Carefully review the error messages. They typically point to incorrect YAML syntax or violations of the Score specification schema. Common mistakes include wrong indentation, missing required fields, or incorrect data types. Refer to the official Score specification reference for the correct schema.

    score validate -f score.yaml

    Example Error:

    Error: score.yaml: containers.my-app.image: Invalid type. Expected: string, given: integer
  2. Issue: score-helm run fails or produces an empty/incorrect values.yaml.

    Solution: Ensure your score.yaml is valid (run score validate first). Check the Score-Helm output for any warnings or errors. Sometimes, the transformer might not know how to handle certain custom Score fields if the default transformation logic is insufficient. You might need to contribute to Score-Helm or extend it if you have highly custom requirements. Also, ensure the Score-Helm binary is executable and in your PATH.

    score-helm run -f score.yaml -o values.yaml --debug
  3. Issue: Helm deployment fails with template errors (e.g., “Error: render error in ‘hello-score-chart/templates/deployment.yaml’: template: hello-score-chart/templates/deployment.yaml:1: function “Values” not defined”).

    Solution: This usually means your Helm chart templates are trying to access a value that doesn’t exist in the values.yaml generated by score-helm, or the path to the value is incorrect. Compare the structure of your values.yaml with how your Helm templates are trying to access the values (e.g., .Values.image.repository). Adjust either the Helm template or investigate why score-helm isn’t generating the expected value.

    helm template hello-score ./hello-score-chart -f ../values.yaml --debug
  4. Issue: Application Pods are stuck in Pending state.

    Solution: This often indicates resource constraints or scheduling issues. Check kubectl describe pod for events related to insufficient CPU, memory, or storage. Ensure your cluster has enough nodes and resources. If using custom resource types (e.g., GPUs), verify the necessary device plugins are installed and configured. For advanced node autoscaling, consider Karpenter to dynamically provision nodes.

    kubectl get events --field-selector involvedObject.name=<pod-name>
  5. Issue: Application Pods are crashing or in a CrashLoopBackOff state.

    Solution: The application itself is failing. Check the Pod logs: kubectl logs . Common causes include incorrect environment variables, missing configuration files, issues with database connections, or application code bugs. Verify that the environment variables set in your Score file are correctly passed to the container and that any declared resources (like databases) are accessible.

    kubectl logs <pod-name> --previous # if it restarted
  6. Issue: Service is deployed, but application is not accessible.

    Solution:

    1. Check Service Selector: Ensure the Service’s selector matches the Pods’ labels.
    2. Check Port Configuration: Verify the Service’s port and targetPort match the container’s exposed port.
    3. Network Policies: If Kubernetes Network Policies are in place, they might be blocking traffic. Check policies and logs of your CNI (e.g., Cilium logs for Cilium WireGuard Encryption).
    4. Ingress/Gateway: If using Ingress or Gateway API, ensure its rules correctly route to your Service.
    5. Firewall: For external access (LoadBalancer), check cloud provider firewalls.
    kubectl describe service <service-name>
    kubectl get pods --show-labels # to verify labels

FAQ Section

  1. What is Score and why should I use it?

    Score is an open-source workload specification that provides a simplified, environment-agnostic way to describe an application’s requirements. You should use it to decouple application definitions from infrastructure details, enabling developers to define workloads once and platform teams to translate them consistently into various platform-specific configurations (like Kubernetes YAML), reducing complexity and promoting standardization.

  2. How is Score different from Helm?

    Score defines what an application needs, focusing on application requirements. Helm is a package manager that defines how an application is deployed on Kubernetes, focusing on templating and managing Kubernetes manifests. Score complements Helm: Score-Helm transforms a Score file into Helm values, which are then consumed by a generic Helm chart to generate the final Kubernetes manifests.

  3. Can Score replace all my Kubernetes YAML?

    No, Score doesn’t replace Kubernetes YAML directly. It abstracts away the complexity of writing Kubernetes YAML for developers. Platform teams still manage the underlying Kubernetes YAML within generic Helm charts (or other transformers) that interpret the Score specification. This allows platform teams to maintain control over infrastructure best practices, security, and operational concerns, while developers focus on application logic.

  4. Is Score only for Kubernetes?

    No. While Score-Helm is a popular transformer for Kubernetes, the Score specification itself is platform-agnostic. The vision is to have transformers for various platforms, such as AWS CloudFormation, Azure ARM templates, Google Cloud Deployment Manager, or even serverless platforms. This allows for true write-once, deploy-anywhere capabilities.

  5. How does Score handle sensitive information like API keys or database passwords?

    Score defines placeholders for sensitive information using the ${resources..} syntax (e.g., DATABASE_URL: ${resources.db.connectionString}). It is the responsibility of the platform team’s Helm chart (or other transformers) and the underlying Kubernetes cluster to inject actual secrets securely. This typically involves integrating with Kubernetes Secrets, Secret Managers (like Vault), or cloud-specific secret stores.

Cleanup Commands

To remove the resources created during this tutorial, use the following commands:

helm uninstall hello-score
rm score.yaml values.yaml
rm -rf hello-score-chart

Expected Output:

release "hello-score" uninstalled

Next Steps / Further Reading

You’ve taken your first steps with Score! To deepen your understanding and leverage its full potential, consider exploring these areas:

  • Official Score Documentation: Dive into the official Score documentation to understand the full specification and advanced features.
  • Score-Helm Advanced Usage: Explore how to customize the Score-Helm transformer and write more complex Helm charts that consume Score files effectively. The Score-Helm GitHub repository is a great resource.
  • Crossplane Integration: Learn how to combine Score with Crossplane to provision cloud resources declared in your Score file directly from Kubernetes.
  • Platform Engineering Best Practices: Understand how Score fits into a broader platform engineering strategy.
  • Advanced Kubernetes Networking: For complex traffic management, explore the Kubernetes Gateway API or service meshes like Istio Ambient Mesh.
  • Kubernetes Security: Enhance your cluster’s security posture with Sigstore and Kyverno for supply chain integrity and policy enforcement.
  • Observability with eBPF: Gain deeper insights into your applications and cluster with tools like eBPF Observability with Hubble.

Conclusion

The Score workload specification offers a compelling solution to the persistent challenge of bridging the gap between developers and infrastructure. By providing a clean, platform-agnostic contract, Score empowers developers to define their application’s needs intuitively, while giving platform teams the flexibility to implement robust, standardized, and secure deployment pipelines. This separation of concerns not only streamlines the development workflow but also enhances operational consistency and reduces the cognitive load on both sides of the development-operations divide.

As Kubernetes environments grow in complexity, tools like Score become indispensable for scaling platform engineering efforts. Embracing Score can lead to faster deployments, fewer errors, and a more harmonious relationship between application developers and the platform teams that support them. Start experimenting with Score today, and witness how it can simplify your Kubernetes journey.

Leave a comment