[{"data":1,"prerenderedAt":761},["ShallowReactive",2],{"/en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend":3,"navigation-en-us":38,"banner-en-us":465,"footer-en-us":482,"Anna Vovchenko":727,"next-steps-en-us":740,"footer-source-/en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend/":755},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":27,"_id":31,"_type":32,"title":33,"_source":34,"_file":35,"_stem":36,"_extension":37},"/en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Kubernetes overview: Operate cluster data on the frontend","GitLab offers a built-in solution for monitoring your Kubernetes cluster health. Learn more about the technical design and functionality with this detailed guide.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099045/Blog/Hero%20Images/Blog/Hero%20Images/blog-image-template-1800x945%20%2816%29_3L7ZP4GxJrShu6qImuS4Wo_1750099045397.png","https://about.gitlab.com/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Kubernetes overview: Operate cluster data on the frontend\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Anna Vovchenko\"}],\n        \"datePublished\": \"2024-06-20\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"updatedDate":20,"body":21,"category":22,"tags":23},[18],"Anna Vovchenko","2024-06-20","2025-09-17","Accessing real-time cluster information is crucial for verifying successful\nsoftware deployments and initiating troubleshooting processes. In this\narticle, you'll learn about GitLab's enhanced Kubernetes integration,\nincluding how to leverage the Watch API for real-time insights into\ndeployment statuses and streamlined troubleshooting capabilities. \n\n\n## What are GitLab's Kubernetes resources?\n\n\nGitLab offers a dedicated [dashboard for\nKubernetes](https://gitlab.com/groups/gitlab-org/-/epics/2493 \"Visualize the\ncluster state in GitLab\") to understand the status of connected clusters\nwith an intuitive visual interface. It is integrated into the Environment\nDetails page and shows resources relevant to the environment. Currently,\nthree types of Kubernetes resources are available:\n\n\n- pods filtered by the Kubernetes namespace\n\n- services\n\n- Flux resource\n([HelmRelease](https://fluxcd.io/flux/components/helm/helmreleases/) or\n[Kustomization](https://fluxcd.io/flux/components/kustomize/kustomizations/))\n\n\nFor these resources, we provide general information, such as name, status,\nnamespace, age, etc. It is represented similarly to what the\n[kubectl](https://kubernetes.io/docs/reference/kubectl/) command would show\nwhen run from the Kubernetes cluster. More details can be found when\nclicking each resource. The side drawer shows the list of labels, annotations,\ndetailed status, and spec information presented as read-only YAML code blocks,\nand streams real-time Kubernetes events filtered for that specific resource.\nFor pods, users can navigate to a dedicated logs view where they select a\ncontainer and stream its real-time logs, providing crucial debugging information.\n\n\nThe information provided helps to visualize the cluster state, spot any\nissues, and debug problematic deployments right away. Users can also take\nimmediate action — deleting failed pods, triggering Flux reconciliation, or\nsuspending/resuming synchronization — all from the same interface without\ncontext switching to command-line tools.\n\n\n## Frontend to cluster communication: The GitLab solution\n\n\nWe have developed a range of tools and solutions to enable a seamless\nconnection and management of Kubernetes clusters within GitLab. One of the\ncore components of this system is the [GitLab agent for\nKubernetes](https://docs.gitlab.com/ee/user/clusters/agent/install/). This\npowerful tool provides a secure bidirectional connection between a GitLab\ninstance and a Kubernetes cluster. It is composed of two main components:\n**agentk** and **KAS** (Kubernetes agent server).\n\n\n![Kubernetes flow\nchart](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099055229.png)\n\n\nagentk is a lightweight cluster-side component. It is responsible for\nestablishing a connection to a KAS instance and waiting for requests to\nprocess. It is proxying requests from KAS to Kubernetes API. It may also\nactively send information about cluster events to KAS.\n\n\nWhile agentk is actively communicating with the cluster, KAS represents a\nGitLab server-side component. It is responsible for:\n\n\n- accepting requests from agentk\n\n- authenticating agentk requests by querying GitLab backend\n\n- fetching the agent's configuration from a corresponding Git repository\nusing Gitaly\n\n- polling manifest repositories for GitOps support\n\n\nWe implemented the agent access rights feature to provide access from the\nGitLab frontend to the cluster in a secure and reliable way. To enable the\nfeature, the user should update the agent's configuration file by adding the\n[user_access](https://docs.gitlab.com/ee/user/clusters/agent/user_access.html)\nsection with the following parameters: `projects`, `groups`, and `access_as`\nto specify which projects can access cluster information via the agent and\nhow it should authenticate.\n\n\nOnce this is done, the frontend can connect to the cluster by sending a\nrequest to the Rails controller, which should set a `gitlab_kas cookie`.\nThis cookie is then added to the request sent to KAS together with the agent\nID and Cross-Site Request Forgery (CSRF) token. Upon receiving the request,\nKAS checks the user's authorization and forwards it to agentk, which makes\nan actual request to the Kubernetes API. Then the response goes all the way\nback from the agentk to KAS and finally to the GitLab client.\n\n\n![Kubernetes overview - how it\nworks](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750099055229.png)\n\n\nTo integrate this logic on the GitLab frontend and use it within the Vue\napp, we developed a JavaScript library:\n[@gitlab/cluster-client](https://gitlab.com/gitlab-org/cluster-integration/javascript-client).\nIt is generated from the Kubernetes OpenAPI specification using the\ntypescript-fetch generator. It provides all the Kubernetes APIs in a way\nthat can be used in a web browser, along with our `WebSocketWatchManager`\nclass that handles the Watch Aggregator API for efficient real-time updates.\n\n\n## Introducing the Watch API\n\n\nThe most challenging task is to provide **real-time updates** for the\nKubernetes dashboard. Kubernetes introduces the concept of watches as an\nextension of GET requests, exposing the body contents as a [readable\nstream](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_streams).\nOnce connected to the stream, the Kubernetes API pushes cluster state\nupdates similarly to how the `kubectl get \u003Cresource> --watch` command works.\nThe watch mechanism allows a client to fetch the current state of the\nresource (or resources list) and then subscribe to subsequent changes,\nwithout missing any events. Each event contains a type of modification (one\nof three types: added, modified, or deleted) and the affected object.\n\n\nWithin the `WatchApi` class of the `@gitlab/cluster-client` library, we've\ndeveloped a systematic approach for interacting with the Kubernetes API.\nThis involves fetching a continuous stream of data, processing it line by\nline, and managing events based on their types. Let's explore the key\ncomponents and functionalities of this approach:\n\n\n1. Extending the Kubernetes API: Within the `WatchApi` class, we extend the\nbase Kubernetes API functionality to fetch a continuous stream of data with\na specified path and query parameters. This extension enables efficient\nhandling of large datasets, as the stream is processed line by line.\n2. Decoding and event categorization: Upon receiving the stream, each line,\ntypically representing a JSON object, is decoded. This process extracts relevant\ninformation and categorizes events based on their types.\n3. Internal data management: The `WatchApi` class maintains an internal data\narray to represent the current state of the streamed data, updating it\naccordingly as new data arrives or changes occur. \n\n4. The `WatchApi` class implements methods for registering event listeners,\nsuch as `onData`, `onError`, `onTimeout`, and `onTerminate`. These methods\nallow developers to customize their application's response to events like\ndata updates, errors, and timeouts. \n\n\nThe code also handles scenarios such as invalid content types, timeouts, and\nerrors from the server, emitting corresponding events for clients to handle\nappropriately. **With this straightforward, event-driven approach, the\n`WatchApi` class allows developers to create responsive real-time\napplications efficiently.**\n\n\n![Kubernetes overview - flow\nchart](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750099055231.png)\n\n## Evolution to WebSocket Aggregation\n\nBuilding on this Watch API foundation, we've introduced the `WebSocketWatchManager`\nto address the limitations of readable streams. This enhanced approach uses the\n`gitlab-agent-watch-api` WebSocket subprotocol to aggregate multiple resource\nwatches within a single persistent connection, bringing several improvements:\n\n- **Multiple watches through one connection:** Instead of opening separate HTTP\nstreams for each Kubernetes resource, they all share a single WebSocket connection.\nThis significantly reduces the connection overhead on both client and server,\nespecially important when monitoring numerous resources across different namespaces.\n\n- **Dynamic watch management:** The protocol supports starting and stopping\nindividual watches on demand through `watch` and `unwatch` messages. The `unwatch`\nfunctionality isn't yet implemented in the JavaScript client, but once added,\nit will stop unnecessary watches when users navigate away from resource views.\n\n- **More reliable connections:** WebSocket connections maintain stable, long-lived\nsessions without the unpredictable drops we experienced with the Watch API's\nHTTP-based streaming. These streaming connections would sometimes terminate unexpectedly,\nrequiring reconnection logic and potentially missing events during the reconnection window.\n\n\nWhen establishing a connection, the client must first request an authentication token\nbecause WebSocket connections cannot send custom headers (like CSRF tokens or cookies)\nafter the initial handshake. This token, obtained via a standard HTTP request with full\nauthentication headers, is then embedded in the WebSocket subprotocol header during\nconnection establishment. Once connected to the `/watch` endpoint, the client starts\nindividual watches by sending messages with resource parameters, and the server\nstreams back watch events as Kubernetes resources change.\n\n\nThe system falls back to the original `WatchApi` implementation when WebSocket\nconnections fail for any reason — whether due to network infrastructure limitations,\nconnection failures, or authentication issues. This dual approach delivers WebSocket\nperformance benefits where possible, while maintaining full functionality through the\nWatch API's streaming method when needed. The switch happens automatically, delivering\nconsistent real-time functionality without requiring user awareness or intervention.\n\n\n## How is the Kubernetes overview integrated with the GitLab frontend?\n\n\nCurrently, we have two Kubernetes integrations within the product: the\nKubernetes overview section for the Environments and the full Kubernetes\ndashboard as a separate view. The latter is a major effort of representing\nall the available Kubernetes resources with filtering and sorting\ncapabilities and a detailed view with the full information on the metadata,\nspec, and status of the resource. This initiative is now on hold while we\nare searching for the most useful ways of representing the Kubernetes\nresources related to an environment.\n\n\n[The Kubernetes\noverview](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html)\non the Environments page is a detailed view of the Kubernetes resources\nrelated to a specific environment. To access the cluster state view, the\nuser should select an agent installed in the cluster with the appropriate\naccess rights, provide a namespace (optionally), and select a related Flux\nresource.\n\n\nThe view renders a list of Kubernetes pods and services filtered by the\nnamespace representing their statuses as well as the Flux sync status.\nClicking each resource opens a detailed view with more information for easy\nissue spotting and high-level debugging. \n\n\n![Kubernetes overview - list of Kubernetes pods and\nservices](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099055233.png)\n\n\nWe need to set up a correct configuration object that will be used for all\nthe API requests. In the configuration, we need to specify the URL provided\nby the KAS, that proxies the Kubernetes APIs; the GitLab agent ID to connect\nwith; and the CSRF token. We need to include cookies so that the\n`kas_cookie` gets picked up and sent within the request.\n\n\n```javascript\n\ncreateK8sAccessConfig({ kasTunnelUrl, gitlabAgentId }) {\n  return {\n    basePath: kasTunnelUrl,\n    headers: {\n      'GitLab-Agent-Id': gitlabAgentId,\n      ...csrf.headers,\n    },\n    credentials: 'include',\n  };\n}\n\n```\n\nThis configuration object serves as the foundation for all Kubernetes API interactions\nthrough KAS, whether using standard API calls, Watch API streaming, or WebSocket\nconnections. Each connection method builds upon this base configuration with its specific\nrequirements.\n\n\nAll the API requests are implemented as GraphQL client queries for\nefficiency, flexibility, and ease of development. The query structure\nenables clients to fetch data from various sources in one request. With\nclear schema definitions, GraphQL minimizes errors and enhances developer\nefficiency. The WebSocket implementation complements this by managing\nreal-time updates through a single persistent connection, reducing\nthe need for multiple parallel streaming connections while maintaining\nthe same GraphQL query structure for data updates.\n\n\nWhen first rendering the Kubernetes overview, the frontend requests static\nlists of pods, services, and Flux resource (either HelmRelease or\nKustomization). The fetch request is needed to render the empty view\ncorrectly. If the frontend tried to subscribe to the Watch API stream and\none of the resource lists was empty, we would wait for the updates forever\nand never show the actual result — 0 resources. In the case of pods and\nservices, after the initial request, we subscribe to the stream even if an\nempty list was received to reflect any cluster state changes. For the Flux\nresource, the changes that the user would expect the resource to appear\nafter the initial request are low. We use the empty response here as an\nopportunity to provide more information about the feature and its setup. \n\n\n![Kubernetes overview - flux sync status\nunavailable](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099055235.png)\n\n\nAfter rendering the initial result, the frontend establishes real-time\nconnections to monitor changes. The implementation attempts the WebSocket\nconnection first, falling back to the Watch API streaming when unavailable.\nWith WebSocket connections, a single connection handles all resource watches,\nwhich maintains internal streams for each resource and updates them based on\nincoming events (`ADDED`, `MODIFIED`, `DELETED`). In Watch API fallback mode,\nthe frontend makes requests with the `?watch=true` query parameter, creating\nseparate watchers for each event type:\n\n\n```javascript\n\nwatcher.on(EVENT_DATA, (data) => {\n  result = data.map(mapWorkloadItem);\n  client.writeQuery({\n    query,\n    variables: { configuration, namespace },\n    data: { [queryField]: result },\n  });\n\n  updateConnectionStatus(client, {\n    configuration,\n    namespace,\n    resourceType: queryField,\n    status: connectionStatus.connected,\n  });\n});\n\n```\n\nBoth approaches follow the same data processing pattern:\n\n- Transform the data to ensure a consistent structure\n\n- Update the Apollo cache to trigger UI updates\n\n- Run a mutation to update the connection status indicator\n\n\nAs we show the detailed information for each resource, we rely on having the\nstatus, spec, and metadata fields with the annotations and labels included.\nThe Kubernetes API wouldn't always send this information, which could break\nthe UI and throw errors from the GraphQL client. We transform the received\ndata first to avoid these issues. We also add the `__typename` so that we\ncan better define the data types and simplify the queries by reusing the\nshared fragments.\n\n\nAfter data stabilization, we update the Apollo cache so that the frontend\nre-renders the views accordingly to reflect cluster state changes.\nInterestingly, we can visualize exactly what happens in the cluster — for\nexample, when deleting the pods, Kubernetes first creates the new ones in\nthe pending state, and only then removes the old pods. Thus, for a moment, we\ncan see double the amount of pods. We can also verify how the pods proceed\nfrom one state to another in real-time. This is done with the combination of\nadded, deleted, and modified events received from the Kubernetes APIs and\nprocessed by either the `WebSocketWatchManager` or the` WatchApi` class of\nthe `@gitlab/cluster-client` library.\n\n\n![Kubernetes overview - states of connection\nstatus](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099055/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099055236.gif)\n\n\nThe connection status indicator remains important for both connection types,\nthough for different reasons. With WebSocket connections, while they're more\nstable and long-lived, users still need to know if the connection drops due\nto network issues or authentication expiry. With Watch API streaming, the\nconnections have timeout limitations and are more prone to disconnection.\nTo achieve this visibility, we introduced a `k8sConnection` query together with\nthe `reconnectToCluster` mutation. The UI displays a badge with a tooltip showing\nthree connection states:\n\n- **Connecting:** Set when initiating either the connection\n\n- **Connected:** Updated when the first data arrives through either connection type\n\n- **Disconnected:** Triggered when WebSocket connections fail, Watch API streams timeout, or any connection errors occur\n\n\nWhen disconnection occurs, users can click to reconnect without refreshing the browser.\nRelying on the user action to reconnect to the stream helps us save resources and only\nrequest the necessary data while ensuring the accurate cluster state is available for\nthe user at any time.\n\n\n## What's next?\n\n\nLeveraging the Kubernetes built-in functionality for watching the Readable\nstream helped us to build the functionality quickly and provide the\nKubernetes UI solution to our customers, getting early feedback and\nadjusting the product direction. The initial Watch API approach, while\npresenting challenges like connection timeouts and reconnection needs,\ngave us valuable insights into real-world usage patterns. The WebSocket\nimplementation has addressed many of these initial challenges, providing\nmore stable connections and better resource efficiency through multiplexed watches.\nBoth the Watch API and WebSocket approaches continue to evolve based on user needs.\n\n\nThere are opportunities for enhancement that could improve the user experience further.\nThe WebSocket protocol already supports `unwatch` messages for dynamic watch management,\nthough this isn't yet implemented in the JavaScript client. Fuller error handling and\nsupport for additional Kubernetes resource types could also expand the dashboard's capabilities.\n\n\nIf you're interested in contributing to the Kubernetes dashboard functionality,\nexploring enhancement opportunities, or sharing your use cases, join the\ndiscussion in our [Kubernetes Dashboard epic](https://gitlab.com/groups/gitlab-org/-/epics/2493).\n\n\n> Curious to learn more or want to try out this functionality? Visit our\n[Kubernetes Dashboard\ndocumentation](https://docs.gitlab.com/ee/ci/environments/kubernetes_dashboard.html)\nfor more details and configuration tips.\n","engineering",[24,25,26],"kubernetes","features","tutorial",{"slug":28,"featured":29,"template":30},"kubernetes-overview-operate-cluster-data-on-the-frontend",true,"BlogPost","content:en-us:blog:kubernetes-overview-operate-cluster-data-on-the-frontend.yml","yaml","Kubernetes Overview Operate Cluster Data On The Frontend","content","en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend.yml","en-us/blog/kubernetes-overview-operate-cluster-data-on-the-frontend","yml",{"_path":39,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":41,"_id":461,"_type":32,"title":462,"_source":34,"_file":463,"_stem":464,"_extension":37},"/shared/en-us/main-navigation","en-us",{"logo":42,"freeTrial":47,"sales":52,"login":57,"items":62,"search":392,"minimal":423,"duo":442,"pricingDeployment":451},{"config":43},{"href":44,"dataGaName":45,"dataGaLocation":46},"/","gitlab logo","header",{"text":48,"config":49},"Get free trial",{"href":50,"dataGaName":51,"dataGaLocation":46},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":53,"config":54},"Talk to sales",{"href":55,"dataGaName":56,"dataGaLocation":46},"/sales/","sales",{"text":58,"config":59},"Sign in",{"href":60,"dataGaName":61,"dataGaLocation":46},"https://gitlab.com/users/sign_in/","sign in",[63,107,203,208,313,373],{"text":64,"config":65,"cards":67,"footer":90},"Platform",{"dataNavLevelOne":66},"platform",[68,74,82],{"title":64,"description":69,"link":70},"The most comprehensive AI-powered DevSecOps Platform",{"text":71,"config":72},"Explore our Platform",{"href":73,"dataGaName":66,"dataGaLocation":46},"/platform/",{"title":75,"description":76,"link":77},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":78,"config":79},"Meet GitLab Duo",{"href":80,"dataGaName":81,"dataGaLocation":46},"/gitlab-duo/","gitlab duo ai",{"title":83,"description":84,"link":85},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":86,"config":87},"Learn more",{"href":88,"dataGaName":89,"dataGaLocation":46},"/why-gitlab/","why gitlab",{"title":91,"items":92},"Get started with",[93,98,103],{"text":94,"config":95},"Platform Engineering",{"href":96,"dataGaName":97,"dataGaLocation":46},"/solutions/platform-engineering/","platform engineering",{"text":99,"config":100},"Developer Experience",{"href":101,"dataGaName":102,"dataGaLocation":46},"/developer-experience/","Developer experience",{"text":104,"config":105},"MLOps",{"href":106,"dataGaName":104,"dataGaLocation":46},"/topics/devops/the-role-of-ai-in-devops/",{"text":108,"left":29,"config":109,"link":111,"lists":115,"footer":185},"Product",{"dataNavLevelOne":110},"solutions",{"text":112,"config":113},"View all Solutions",{"href":114,"dataGaName":110,"dataGaLocation":46},"/solutions/",[116,141,164],{"title":117,"description":118,"link":119,"items":124},"Automation","CI/CD and automation to accelerate deployment",{"config":120},{"icon":121,"href":122,"dataGaName":123,"dataGaLocation":46},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[125,129,133,137],{"text":126,"config":127},"CI/CD",{"href":128,"dataGaLocation":46,"dataGaName":126},"/solutions/continuous-integration/",{"text":130,"config":131},"AI-Assisted Development",{"href":80,"dataGaLocation":46,"dataGaName":132},"AI assisted development",{"text":134,"config":135},"Source Code Management",{"href":136,"dataGaLocation":46,"dataGaName":134},"/solutions/source-code-management/",{"text":138,"config":139},"Automated Software Delivery",{"href":122,"dataGaLocation":46,"dataGaName":140},"Automated software delivery",{"title":142,"description":143,"link":144,"items":149},"Security","Deliver code faster without compromising security",{"config":145},{"href":146,"dataGaName":147,"dataGaLocation":46,"icon":148},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[150,154,159],{"text":151,"config":152},"Application Security Testing",{"href":146,"dataGaName":153,"dataGaLocation":46},"Application security testing",{"text":155,"config":156},"Software Supply Chain Security",{"href":157,"dataGaLocation":46,"dataGaName":158},"/solutions/supply-chain/","Software supply chain security",{"text":160,"config":161},"Software Compliance",{"href":162,"dataGaName":163,"dataGaLocation":46},"/solutions/software-compliance/","software compliance",{"title":165,"link":166,"items":171},"Measurement",{"config":167},{"icon":168,"href":169,"dataGaName":170,"dataGaLocation":46},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[172,176,180],{"text":173,"config":174},"Visibility & Measurement",{"href":169,"dataGaLocation":46,"dataGaName":175},"Visibility and Measurement",{"text":177,"config":178},"Value Stream Management",{"href":179,"dataGaLocation":46,"dataGaName":177},"/solutions/value-stream-management/",{"text":181,"config":182},"Analytics & Insights",{"href":183,"dataGaLocation":46,"dataGaName":184},"/solutions/analytics-and-insights/","Analytics and insights",{"title":186,"items":187},"GitLab for",[188,193,198],{"text":189,"config":190},"Enterprise",{"href":191,"dataGaLocation":46,"dataGaName":192},"/enterprise/","enterprise",{"text":194,"config":195},"Small Business",{"href":196,"dataGaLocation":46,"dataGaName":197},"/small-business/","small business",{"text":199,"config":200},"Public Sector",{"href":201,"dataGaLocation":46,"dataGaName":202},"/solutions/public-sector/","public sector",{"text":204,"config":205},"Pricing",{"href":206,"dataGaName":207,"dataGaLocation":46,"dataNavLevelOne":207},"/pricing/","pricing",{"text":209,"config":210,"link":212,"lists":216,"feature":300},"Resources",{"dataNavLevelOne":211},"resources",{"text":213,"config":214},"View all resources",{"href":215,"dataGaName":211,"dataGaLocation":46},"/resources/",[217,250,272],{"title":218,"items":219},"Getting started",[220,225,230,235,240,245],{"text":221,"config":222},"Install",{"href":223,"dataGaName":224,"dataGaLocation":46},"/install/","install",{"text":226,"config":227},"Quick start guides",{"href":228,"dataGaName":229,"dataGaLocation":46},"/get-started/","quick setup checklists",{"text":231,"config":232},"Learn",{"href":233,"dataGaLocation":46,"dataGaName":234},"https://university.gitlab.com/","learn",{"text":236,"config":237},"Product documentation",{"href":238,"dataGaName":239,"dataGaLocation":46},"https://docs.gitlab.com/","product documentation",{"text":241,"config":242},"Best practice videos",{"href":243,"dataGaName":244,"dataGaLocation":46},"/getting-started-videos/","best practice videos",{"text":246,"config":247},"Integrations",{"href":248,"dataGaName":249,"dataGaLocation":46},"/integrations/","integrations",{"title":251,"items":252},"Discover",[253,258,262,267],{"text":254,"config":255},"Customer success stories",{"href":256,"dataGaName":257,"dataGaLocation":46},"/customers/","customer success stories",{"text":259,"config":260},"Blog",{"href":261,"dataGaName":5,"dataGaLocation":46},"/blog/",{"text":263,"config":264},"Remote",{"href":265,"dataGaName":266,"dataGaLocation":46},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":268,"config":269},"TeamOps",{"href":270,"dataGaName":271,"dataGaLocation":46},"/teamops/","teamops",{"title":273,"items":274},"Connect",[275,280,285,290,295],{"text":276,"config":277},"GitLab Services",{"href":278,"dataGaName":279,"dataGaLocation":46},"/services/","services",{"text":281,"config":282},"Community",{"href":283,"dataGaName":284,"dataGaLocation":46},"/community/","community",{"text":286,"config":287},"Forum",{"href":288,"dataGaName":289,"dataGaLocation":46},"https://forum.gitlab.com/","forum",{"text":291,"config":292},"Events",{"href":293,"dataGaName":294,"dataGaLocation":46},"/events/","events",{"text":296,"config":297},"Partners",{"href":298,"dataGaName":299,"dataGaLocation":46},"/partners/","partners",{"backgroundColor":301,"textColor":302,"text":303,"image":304,"link":308},"#2f2a6b","#fff","Insights for the future of software development",{"altText":305,"config":306},"the source promo card",{"src":307},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":309,"config":310},"Read the latest",{"href":311,"dataGaName":312,"dataGaLocation":46},"/the-source/","the source",{"text":314,"config":315,"lists":317},"Company",{"dataNavLevelOne":316},"company",[318],{"items":319},[320,325,331,333,338,343,348,353,358,363,368],{"text":321,"config":322},"About",{"href":323,"dataGaName":324,"dataGaLocation":46},"/company/","about",{"text":326,"config":327,"footerGa":330},"Jobs",{"href":328,"dataGaName":329,"dataGaLocation":46},"/jobs/","jobs",{"dataGaName":329},{"text":291,"config":332},{"href":293,"dataGaName":294,"dataGaLocation":46},{"text":334,"config":335},"Leadership",{"href":336,"dataGaName":337,"dataGaLocation":46},"/company/team/e-group/","leadership",{"text":339,"config":340},"Team",{"href":341,"dataGaName":342,"dataGaLocation":46},"/company/team/","team",{"text":344,"config":345},"Handbook",{"href":346,"dataGaName":347,"dataGaLocation":46},"https://handbook.gitlab.com/","handbook",{"text":349,"config":350},"Investor relations",{"href":351,"dataGaName":352,"dataGaLocation":46},"https://ir.gitlab.com/","investor relations",{"text":354,"config":355},"Trust Center",{"href":356,"dataGaName":357,"dataGaLocation":46},"/security/","trust center",{"text":359,"config":360},"AI Transparency Center",{"href":361,"dataGaName":362,"dataGaLocation":46},"/ai-transparency-center/","ai transparency center",{"text":364,"config":365},"Newsletter",{"href":366,"dataGaName":367,"dataGaLocation":46},"/company/contact/","newsletter",{"text":369,"config":370},"Press",{"href":371,"dataGaName":372,"dataGaLocation":46},"/press/","press",{"text":374,"config":375,"lists":376},"Contact us",{"dataNavLevelOne":316},[377],{"items":378},[379,382,387],{"text":53,"config":380},{"href":55,"dataGaName":381,"dataGaLocation":46},"talk to sales",{"text":383,"config":384},"Support portal",{"href":385,"dataGaName":386,"dataGaLocation":46},"https://support.gitlab.com","support portal",{"text":388,"config":389},"Customer portal",{"href":390,"dataGaName":391,"dataGaLocation":46},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":393,"login":394,"suggestions":401},"Close",{"text":395,"link":396},"To search repositories and projects, login to",{"text":397,"config":398},"gitlab.com",{"href":60,"dataGaName":399,"dataGaLocation":400},"search login","search",{"text":402,"default":403},"Suggestions",[404,406,410,412,416,420],{"text":75,"config":405},{"href":80,"dataGaName":75,"dataGaLocation":400},{"text":407,"config":408},"Code Suggestions (AI)",{"href":409,"dataGaName":407,"dataGaLocation":400},"/solutions/code-suggestions/",{"text":126,"config":411},{"href":128,"dataGaName":126,"dataGaLocation":400},{"text":413,"config":414},"GitLab on AWS",{"href":415,"dataGaName":413,"dataGaLocation":400},"/partners/technology-partners/aws/",{"text":417,"config":418},"GitLab on Google Cloud",{"href":419,"dataGaName":417,"dataGaLocation":400},"/partners/technology-partners/google-cloud-platform/",{"text":421,"config":422},"Why GitLab?",{"href":88,"dataGaName":421,"dataGaLocation":400},{"freeTrial":424,"mobileIcon":429,"desktopIcon":434,"secondaryButton":437},{"text":425,"config":426},"Start free trial",{"href":427,"dataGaName":51,"dataGaLocation":428},"https://gitlab.com/-/trials/new/","nav",{"altText":430,"config":431},"Gitlab Icon",{"src":432,"dataGaName":433,"dataGaLocation":428},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":430,"config":435},{"src":436,"dataGaName":433,"dataGaLocation":428},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":438,"config":439},"Get Started",{"href":440,"dataGaName":441,"dataGaLocation":428},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":443,"mobileIcon":447,"desktopIcon":449},{"text":444,"config":445},"Learn more about GitLab Duo",{"href":80,"dataGaName":446,"dataGaLocation":428},"gitlab duo",{"altText":430,"config":448},{"src":432,"dataGaName":433,"dataGaLocation":428},{"altText":430,"config":450},{"src":436,"dataGaName":433,"dataGaLocation":428},{"freeTrial":452,"mobileIcon":457,"desktopIcon":459},{"text":453,"config":454},"Back to pricing",{"href":206,"dataGaName":455,"dataGaLocation":428,"icon":456},"back to pricing","GoBack",{"altText":430,"config":458},{"src":432,"dataGaName":433,"dataGaLocation":428},{"altText":430,"config":460},{"src":436,"dataGaName":433,"dataGaLocation":428},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":466,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"title":467,"button":468,"image":473,"config":477,"_id":479,"_type":32,"_source":34,"_file":480,"_stem":481,"_extension":37},"/shared/en-us/banner","is now in public beta!",{"text":469,"config":470},"Try the Beta",{"href":471,"dataGaName":472,"dataGaLocation":46},"/gitlab-duo/agent-platform/","duo banner",{"altText":474,"config":475},"GitLab Duo Agent Platform",{"src":476},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":478},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":483,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":484,"_id":723,"_type":32,"title":724,"_source":34,"_file":725,"_stem":726,"_extension":37},"/shared/en-us/main-footer",{"text":485,"source":486,"edit":492,"contribute":497,"config":502,"items":507,"minimal":715},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":487,"config":488},"View page source",{"href":489,"dataGaName":490,"dataGaLocation":491},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":493,"config":494},"Edit this page",{"href":495,"dataGaName":496,"dataGaLocation":491},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":498,"config":499},"Please contribute",{"href":500,"dataGaName":501,"dataGaLocation":491},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":503,"facebook":504,"youtube":505,"linkedin":506},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[508,555,608,652,681],{"title":204,"links":509,"subMenu":524},[510,514,519],{"text":511,"config":512},"View plans",{"href":206,"dataGaName":513,"dataGaLocation":491},"view plans",{"text":515,"config":516},"Why Premium?",{"href":517,"dataGaName":518,"dataGaLocation":491},"/pricing/premium/","why premium",{"text":520,"config":521},"Why Ultimate?",{"href":522,"dataGaName":523,"dataGaLocation":491},"/pricing/ultimate/","why ultimate",[525],{"title":526,"links":527},"Contact Us",[528,531,533,535,540,545,550],{"text":529,"config":530},"Contact sales",{"href":55,"dataGaName":56,"dataGaLocation":491},{"text":383,"config":532},{"href":385,"dataGaName":386,"dataGaLocation":491},{"text":388,"config":534},{"href":390,"dataGaName":391,"dataGaLocation":491},{"text":536,"config":537},"Status",{"href":538,"dataGaName":539,"dataGaLocation":491},"https://status.gitlab.com/","status",{"text":541,"config":542},"Terms of use",{"href":543,"dataGaName":544,"dataGaLocation":491},"/terms/","terms of use",{"text":546,"config":547},"Privacy statement",{"href":548,"dataGaName":549,"dataGaLocation":491},"/privacy/","privacy statement",{"text":551,"config":552},"Cookie preferences",{"dataGaName":553,"dataGaLocation":491,"id":554,"isOneTrustButton":29},"cookie preferences","ot-sdk-btn",{"title":108,"links":556,"subMenu":564},[557,561],{"text":558,"config":559},"DevSecOps platform",{"href":73,"dataGaName":560,"dataGaLocation":491},"devsecops platform",{"text":130,"config":562},{"href":80,"dataGaName":563,"dataGaLocation":491},"ai-assisted development",[565],{"title":566,"links":567},"Topics",[568,573,578,583,588,593,598,603],{"text":569,"config":570},"CICD",{"href":571,"dataGaName":572,"dataGaLocation":491},"/topics/ci-cd/","cicd",{"text":574,"config":575},"GitOps",{"href":576,"dataGaName":577,"dataGaLocation":491},"/topics/gitops/","gitops",{"text":579,"config":580},"DevOps",{"href":581,"dataGaName":582,"dataGaLocation":491},"/topics/devops/","devops",{"text":584,"config":585},"Version Control",{"href":586,"dataGaName":587,"dataGaLocation":491},"/topics/version-control/","version control",{"text":589,"config":590},"DevSecOps",{"href":591,"dataGaName":592,"dataGaLocation":491},"/topics/devsecops/","devsecops",{"text":594,"config":595},"Cloud Native",{"href":596,"dataGaName":597,"dataGaLocation":491},"/topics/cloud-native/","cloud native",{"text":599,"config":600},"AI for Coding",{"href":601,"dataGaName":602,"dataGaLocation":491},"/topics/devops/ai-for-coding/","ai for coding",{"text":604,"config":605},"Agentic AI",{"href":606,"dataGaName":607,"dataGaLocation":491},"/topics/agentic-ai/","agentic ai",{"title":609,"links":610},"Solutions",[611,613,615,620,624,627,631,634,636,639,642,647],{"text":151,"config":612},{"href":146,"dataGaName":151,"dataGaLocation":491},{"text":140,"config":614},{"href":122,"dataGaName":123,"dataGaLocation":491},{"text":616,"config":617},"Agile development",{"href":618,"dataGaName":619,"dataGaLocation":491},"/solutions/agile-delivery/","agile delivery",{"text":621,"config":622},"SCM",{"href":136,"dataGaName":623,"dataGaLocation":491},"source code management",{"text":569,"config":625},{"href":128,"dataGaName":626,"dataGaLocation":491},"continuous integration & delivery",{"text":628,"config":629},"Value stream management",{"href":179,"dataGaName":630,"dataGaLocation":491},"value stream management",{"text":574,"config":632},{"href":633,"dataGaName":577,"dataGaLocation":491},"/solutions/gitops/",{"text":189,"config":635},{"href":191,"dataGaName":192,"dataGaLocation":491},{"text":637,"config":638},"Small business",{"href":196,"dataGaName":197,"dataGaLocation":491},{"text":640,"config":641},"Public sector",{"href":201,"dataGaName":202,"dataGaLocation":491},{"text":643,"config":644},"Education",{"href":645,"dataGaName":646,"dataGaLocation":491},"/solutions/education/","education",{"text":648,"config":649},"Financial services",{"href":650,"dataGaName":651,"dataGaLocation":491},"/solutions/finance/","financial services",{"title":209,"links":653},[654,656,658,660,663,665,667,669,671,673,675,677,679],{"text":221,"config":655},{"href":223,"dataGaName":224,"dataGaLocation":491},{"text":226,"config":657},{"href":228,"dataGaName":229,"dataGaLocation":491},{"text":231,"config":659},{"href":233,"dataGaName":234,"dataGaLocation":491},{"text":236,"config":661},{"href":238,"dataGaName":662,"dataGaLocation":491},"docs",{"text":259,"config":664},{"href":261,"dataGaName":5,"dataGaLocation":491},{"text":254,"config":666},{"href":256,"dataGaName":257,"dataGaLocation":491},{"text":263,"config":668},{"href":265,"dataGaName":266,"dataGaLocation":491},{"text":276,"config":670},{"href":278,"dataGaName":279,"dataGaLocation":491},{"text":268,"config":672},{"href":270,"dataGaName":271,"dataGaLocation":491},{"text":281,"config":674},{"href":283,"dataGaName":284,"dataGaLocation":491},{"text":286,"config":676},{"href":288,"dataGaName":289,"dataGaLocation":491},{"text":291,"config":678},{"href":293,"dataGaName":294,"dataGaLocation":491},{"text":296,"config":680},{"href":298,"dataGaName":299,"dataGaLocation":491},{"title":314,"links":682},[683,685,687,689,691,693,695,699,704,706,708,710],{"text":321,"config":684},{"href":323,"dataGaName":316,"dataGaLocation":491},{"text":326,"config":686},{"href":328,"dataGaName":329,"dataGaLocation":491},{"text":334,"config":688},{"href":336,"dataGaName":337,"dataGaLocation":491},{"text":339,"config":690},{"href":341,"dataGaName":342,"dataGaLocation":491},{"text":344,"config":692},{"href":346,"dataGaName":347,"dataGaLocation":491},{"text":349,"config":694},{"href":351,"dataGaName":352,"dataGaLocation":491},{"text":696,"config":697},"Sustainability",{"href":698,"dataGaName":696,"dataGaLocation":491},"/sustainability/",{"text":700,"config":701},"Diversity, inclusion and belonging (DIB)",{"href":702,"dataGaName":703,"dataGaLocation":491},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":354,"config":705},{"href":356,"dataGaName":357,"dataGaLocation":491},{"text":364,"config":707},{"href":366,"dataGaName":367,"dataGaLocation":491},{"text":369,"config":709},{"href":371,"dataGaName":372,"dataGaLocation":491},{"text":711,"config":712},"Modern Slavery Transparency Statement",{"href":713,"dataGaName":714,"dataGaLocation":491},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":716},[717,719,721],{"text":541,"config":718},{"href":543,"dataGaName":544,"dataGaLocation":491},{"text":546,"config":720},{"href":548,"dataGaName":549,"dataGaLocation":491},{"text":551,"config":722},{"dataGaName":553,"dataGaLocation":491,"id":554,"isOneTrustButton":29},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[728],{"_path":729,"_dir":730,"_draft":6,"_partial":6,"_locale":7,"content":731,"config":735,"_id":737,"_type":32,"title":18,"_source":34,"_file":738,"_stem":739,"_extension":37},"/en-us/blog/authors/anna-vovchenko","authors",{"name":18,"config":732},{"headshot":733,"ctfId":734},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749669159/Blog/Author%20Headshots/anna_vovchenko_headshot.png","4bLGBzB5LA0jYw0y9IqCs2",{"template":736},"BlogAuthor","content:en-us:blog:authors:anna-vovchenko.yml","en-us/blog/authors/anna-vovchenko.yml","en-us/blog/authors/anna-vovchenko",{"_path":741,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"header":742,"eyebrow":743,"blurb":744,"button":745,"secondaryButton":749,"_id":751,"_type":32,"title":752,"_source":34,"_file":753,"_stem":754,"_extension":37},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":48,"config":746},{"href":747,"dataGaName":51,"dataGaLocation":748},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":53,"config":750},{"href":55,"dataGaName":56,"dataGaLocation":748},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":756,"content":757,"config":760,"_id":31,"_type":32,"title":33,"_source":34,"_file":35,"_stem":36,"_extension":37},{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},{"title":9,"description":10,"authors":758,"heroImage":11,"date":19,"updatedDate":20,"body":21,"category":22,"tags":759},[18],[24,25,26],{"slug":28,"featured":29,"template":30},1761814429222]