# Accelerate migration to K8s

Organizations migrate to Kubernetes because it solves critical scalability, reliability, security, and developer-velocity challenges that modern teams face. At its core, Kubernetes provides a standardized, automated, self-healing platform for running applications.

Accelerating a Kubernetes migration is often a strategic engineering decision made to meet certain business requirements, such as lowering infrastructure costs or improving MTTR, in a shorter timeframe.&#x20;

To accelerate a migration to Kubernetes:

* [Launch a Kubernetes Deployment Baseline Scorecard](#create-a-kubernetes-scorecard). Cortex provides a prebuilt Kubernetes [Scorecard](/standardize/scorecards.md) template that you can use to track migration-related controls and standards.&#x20;
* [Launch an Initiative](#create-a-k8s-acceleration-initiative) associated with the Scorecard, which gives your engineers a deadline for when to complete certain goals.
* [Use reports and Cortex MCP](#measuring-success) to better understand progress and next steps.

{% hint style="success" %}
Cortex customer LetsGetCheck cut their Kubernetes migration timeline from 24 months down to 16 months by targeting goals with Initiatives. [Read the full case study here](https://www.cortex.io/case-studies/letsgetchecked).
{% endhint %}

## Create a Kubernetes Scorecard

### Prerequisites

Before using this Scorecard template:

* Ensure you have configured integrations for:
  * Version control: [Azure DevOps](/ingesting-data-into-cortex/integrations/azuredevops.md), [Bitbucket](/ingesting-data-into-cortex/integrations/bitbucket.md), [GitHub](/ingesting-data-into-cortex/integrations/github.md), or [GitLab](/ingesting-data-into-cortex/integrations/gitlab.md).
  * [Kubernetes](/ingesting-data-into-cortex/integrations/kubernetes.md)

### Step 1: Create the Scorecard and configure its basic settings

You can create a Scorecard in the Cortex UI, or you can add it to your workspace via GitOps or the Cortex API.

{% tabs %}
{% tab title="Cortex UI" %}

#### Create Scorecard in the Cortex UI

1. On the [**Scorecards** page](https://app.getcortexapp.com/admin/scorecards) in your workspace, click **Create Scorecard**.
2. On the `Kubernetes Deployment Baseline` template, click **Use**.
3. Configure basic settings, including the Scorecard's name, unique identifier, description, and more.
   * Learn about configuring the basic settings in the [Creating a Scorecard documentation](https://app.gitbook.com/o/RD51qiGImxmmq8NjALb1/s/JW7pYRxS4dHS3Hv6wxve/standardize/scorecards/create).
     {% endtab %}

{% tab title="GitOps or API" %}

#### Create Scorecard via GitOps or API

When following a [GitOps approach](/configure/gitops.md), you can add a Scorecard YAML file to your `.cortex/scorecards` directory in your version control repository. Note that GitOps must be enabled for Scorecards in your [GitOps settings](/configure/settings/gitops-settings.md).

You could also use the [Cortex API,](/api/readme/scorecards.md) where you can submit a Scorecard definition in YAML.&#x20;

<details>

<summary>Kubernetes Scorecard YAML</summary>

Use the YAML file below to add this Scorecard to your workspace via the API or via a GitOps flow.

```yaml
tag: kubernetes-deployment-baseline
name: Kubernetes Deployment Baseline
description: |-
  This Scorecard enforces a minimum set of standards for services running on Kubernetes. It validates resource requests/limits, probes, container image hygiene, and Git branch protection so that workloads are reliable, predictable, and safe to operate in production.
draft: true
notifications:
  enabled: true
  scoreDropNotificationsEnabled: false
exemptions:
  enabled: true
  autoApprove: false
  userSpecificNotifications: false
ladder:
  name: Default Ladder
  levels:
  - name: Foundational container hygiene
    rank: 1
    description: The service is at least containerized in a basic way.
    color: "#c38b5f"
  - name: Runtime ready
    rank: 2
    description: This container behaves predictably when scheduled on Kubernetes.
    color: "#8c9298"
  - name: Kubernetes production ready
    rank: 3
    description: This service is safe to run in production from a governance and reliability
      perspective.
    color: "#cda400"
rules:
- title: Memory limit is set
  description: "Prevents a single pod from consuming all node memory. To ensure all\
    \ containers have memory limits set, this rule compares the total number of containers\
    \ with the number of containers that have memory limits."
  expression: |-
    jq(k8s.spec(), "[.[].template.spec.containers[]] | length") ==
    jq(k8s.spec(), "[.[].template.spec.containers[] | select(.resources.limits.memory != null)] | length")
  identifier: 17376f1a-bfbe-361b-86d7-eac192f729d9
  weight: 1
  level: Runtime ready
- title: Git branch protection
  description: Prevent direct pushes and unsafe merges to the main branch; enforce
    reviews and status checks.
  expression: git.branchProtection() != null
  identifier: 21c30af3-6dd6-3bce-bbe5-56e43549015f
  weight: 1
  level: Kubernetes production ready
  failureMessage: Configure Git branch protection.
- title: CPU request is set
  description: "This rule checks if CPU resource requests are defined for all containers\
    \ in your Kubernetes deployments, which is important for: \n- Kubernetes scheduler\
    \ to properly place pods on nodes\n- CPU throttling behavior (requests affect\
    \ CPU shares)\n- Cluster capacity planning\n- QoS classification (along with limits\
    \ and memory requests)\n\nTo ensure all containers have CPU resource requests,\
    \ this rule compares the total number of containers with the number of containers\
    \ that have CPU resource requests."
  expression: |-
    jq(k8s.spec(), "[.[].template.spec.containers[]] | length") ==
    jq(k8s.spec(), "[.[].template.spec.containers[] | select(.resources.requests.cpu != null)] | length")
  identifier: 22d76d97-33a5-358a-8694-cf35909acc71
  weight: 1
  level: Runtime ready
- title: Memory request is set
  description: "Prevents a single pod from consuming all node memory. To ensure all\
    \ containers have memory limits set, this rule compares the total number of containers\
    \ with the number of containers that have memory limits."
  expression: "jq(k8s.spec(), \"[.[].template.spec.containers[]] | length\") == \n\
    jq(k8s.spec(), \"[.[].template.spec.containers[] | select(.resources.requests.memory\
    \ != null)] | length\")"
  identifier: 46f4d17e-ceab-37dd-9a64-ddac9ee4aa73
  weight: 1
  level: Runtime ready
- title: Runs in cluster
  description: This indicates operational use of Kubernetes.
  expression: k8s != null
  identifier: 6cfcc6f2-53ea-3da8-afaf-8659177d1896
  weight: 1
  level: Foundational container hygiene
- title: No "latest" tag for base image
  description: |-
    This is a best practice rule that ensures all base images have explicit version tags instead of using `:latest`, which:
    - Prevents unpredictable builds when `:latest` changes
    - Ensures build reproducibility
    - Makes it clear what version is being used
  expression: "git.fileContents(\"Dockerfile\").matchesIn(\"^FROM[ \\t]+(?!.*:latest\\\
    b).*$\")"
  identifier: 873c36b3-5a18-3a09-9c58-bff967ffd6bd
  weight: 1
  level: Kubernetes production ready
- title: Liveness probe is set
  description: |-
    This ensures your services have liveness probes configured, which are critical for:

    - Detecting when a container is stuck or deadlocked
    - Automatically restarting unhealthy containers
    - Improving service reliability and reducing MTTR
    - Preventing containers that are "running" but not actually functioning

    To ensure all containers have liveness probes, this rule compares the total number of containers with the number of containers that have liveness probes.
  expression: |-
    jq(k8s.spec(), "[.[].template.spec.containers[]] | length") ==
    jq(k8s.spec(), "[.[].template.spec.containers[] | select(.livenessProbe != null)] | length")
  identifier: b1f477fc-2ec5-39b0-9d48-b9a2922d7623
  weight: 1
  level: Runtime ready
- title: Readiness probe is set
  description: |-
    Ensures the service defines a Kubernetes readiness probe so that traffic is only routed to containers that are fully initialized and able to serve requests. A readiness probe prevents the service mesh or load balancer from sending user traffic to a pod that is still starting up, misconfigured, or temporarily degraded. This improves request success rates, reduces startup-induced errors, and helps maintain overall service reliability.

    To ensure all containers have readiness probes, this rule compares the total number of containers with the number of containers that have readiness probes.
  expression: "jq(k8s.spec(), \"[.[].template.spec.containers[]] | length\") == jq(k8s.spec(),\
    \ \"[.[].template.spec.containers[] | select(.readinessProbe != null)] | length\"\
    )"
  identifier: b4bed489-db7d-3566-826a-c78f6da22190
  weight: 1
  level: Runtime ready
  failureMessage: "This service does not have a Kubernetes readiness probe configured.\
    \ Without a readiness probe, the load balancer may send traffic to pods before\
    \ they are ready, leading to failed requests and degraded reliability. Add a readiness\
    \ probe to ensure only healthy, ready pods receive traffic."
- title: Dockerignore file exists
  description: Keep images lean and builds fast by excluding unnecessary files.
  expression: git.fileExists(".dockerignore")
  identifier: deb30726-2692-3a2d-8858-a828d33fea17
  weight: 1
  level: Foundational container hygiene
- title: Dockerfile exists
  description: Verify the service is containerized using a maintained Dockerfile.
    This enables consistency across different environments.
  expression: git.fileExists("Dockerfile")
  identifier: df928975-90e7-35ab-a05c-67ca7cb034a2
  weight: 1
  level: Foundational container hygiene
filter:
  kind: GENERIC
  types:
    include:
    - service
```

</details>
{% endtab %}
{% endtabs %}

### Step 2: Review and modify rules

Cortex's templated rules are based on common industry standards. See the template in-app for more context on each rule:&#x20;

<details>

<summary>Level 1: Foundational container hygiene</summary>

* Runs in cluster\
  `k8s != null`
* Dockerignore file exists\
  `git.fileExists(".dockerignore")`
* Dockerfile exists\
  `git.fileExists("Dockerfile")`

</details>

<details>

<summary>Level 2: Runtime ready</summary>

* Memory request is set\
  `jq(k8s.spec(), "[.[].template.spec.containers[]] | length") == jq(k8s.spec(), "[.[].template.spec.containers[] | select(.resources.requests.memory != null)] | length")`
* Memory limit is set\
  `jq(k8s.spec(), "[.[].template.spec.containers[]] | length") == jq(k8s.spec(), "[.[].template.spec.containers[] | select(.resources.limits.memory != null)] | length")`
* Liveness probe is set\
  `jq(k8s.spec(), "[.[].template.spec.containers[]] | length") == jq(k8s.spec(), "[.[].template.spec.containers[] | select(.livenessProbe != null)] | length")`
* Readiness probe is set\
  `jq(k8s.spec(), "[.[].template.spec.containers[]] | length") == jq(k8s.spec(), "[.[].template.spec.containers[] | select(.readinessProbe != null)] | length")`
* CPU request is set\
  `jq(k8s.spec(), "[.[].template.spec.containers[]] | length") == jq(k8s.spec(), "[.[].template.spec.containers[] | select(.resources.requests.cpu != null)] | length")`

</details>

<details>

<summary>Level 3: Kubernetes production ready</summary>

* Git branch protection\
  `git.branchProtection() != null`
* No "latest" tag for base image\
  `git.fileContents("Dockerfile").matchesIn("^FROM[ \t]+(?!.*:latest\b).$")`

</details>

You can reorder, delete, and edit rules, add more rules to a level, and assign more points to a rule to signify its importance. Behind each rule is a [Cortex Query Language (CQL) ](/standardize/cql.md)query; you can edit the existing CQL or write your own queries to further refine your rules.&#x20;

## Create a K8s acceleration Initiative

If you notice the team isn't making progress as quickly as expected, or you need to complete certain rules within a specific timeframe, you can create an Initiative to motivate completion.

Follow the steps below to create an Initiative:

<details>

<summary>Create a Kubernetes Initiative</summary>

1. While viewing your Kubernetes Deployment Baseline Scorecard, click **Create Initiative** in the upper right.
2. Configure the Initiative fields, including a descriptive name so your team members understand the purpose of the Initiative. For example, `Accelerate K8s migration`.&#x20;
   * Make sure to enable notifications so users are notified if an entity they own is failing the Initiative's goal.
   * For the goal, you might want to ask your team to complete a certain level, or specific rules. Examples: `Complete all "Runtime ready" level rules by the end of the quarter` or `Make sure a Dockerfile exists for every service by end of month`.
3. Save the Initiative.

After the Initiative is published, entity owners will be notified if their entity is not meeting the goal. They will see action items listed on their [engineering homepage](/streamline/homepage.md) and they will automatically receive a weekly progress update from the Initiative.

Learn more about [creating Initiatives in the docs](/improve/initiatives.md).

</details>

## Measuring success

To understand progress of your Scorecard:

* Ask [Cortex MCP,](/get-started/mcp.md) "How is my Kubernetes Deployment Baseline Scorecard doing?" The MCP will respond with information on the entities that are failing rules and suggested next steps.
* Review reports: The [Bird's Eye report](/improve/reports/birds-eye.md) gives insight into how entities are performing against the Scorecard by visualizing the data as a heat map:<br>

  <figure><img src="/files/GTheqgiY0JFXJ3ChK6rQ" alt="The bird&#x27;s eye report shows Scorecard progress as a heat map."><figcaption></figcaption></figure>

You can also review your Engineering Intelligence metrics for impact on key engineering metrics, such as:

* **MTTR**: Kubernetes best practices, such as liveness probes in place, can result in faster diagnosis and auto-recovery. You may see MTTR decrease.
* **Change failure rate**: With resource limits reducing pod crashes and no "latest" image tag resulting in deterministic builds, you may see more successful deploys on first attempt, thus decreasing change failure rate.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cortex.io/guides/migrations-and-modernization/accelerate-migration-to-k8s.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
