[{"data":1,"prerenderedAt":757},["ShallowReactive",2],{"/en-us/blog/eliminate-risk-with-feature-flags-tutorial":3,"navigation-en-us":36,"banner-en-us":463,"footer-en-us":480,"Cesar Saavedra":723,"next-steps-en-us":736,"footer-source-/en-us/blog/eliminate-risk-with-feature-flags-tutorial/":751},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":26,"_id":29,"_type":30,"title":31,"_source":32,"_file":33,"_stem":34,"_extension":35},"/en-us/blog/eliminate-risk-with-feature-flags-tutorial","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"How to use feature flags to lower risk in deployments","Follow this comprehensive tutorial to learn how to create and use feature flags in your software development environment.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749667743/Blog/Hero%20Images/flags.png","https://about.gitlab.com/blog/eliminate-risk-with-feature-flags-tutorial","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to use feature flags to lower risk in deployments\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Cesar Saavedra\"}],\n        \"datePublished\": \"2023-09-20\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Cesar Saavedra","2023-09-20","Developers typically use advanced techniques like canary, blue/green, and\nincremental deployments to reduce risk when practicing progressive delivery,\na facet of continuous delivery (CD). In this tutorial, we will show you how\nto use feature flags, another progressive delivery option developers can use\nto test while in production.\n\n\n## What is progressive delivery?\n\nProgressive delivery is the ability to test in production while controlling\nyour audience of who can exercise or see updates to an application with a\nhigh level of granularity. This approach can also be thought of as developer\nexperimentation.\n\n\n## What are feature flags\n\nFeature flags enable you to choose what to deploy and who to deploy to in\nproduction. They allow you to define the audience for your application\nupdates as well as the fashion in which they will be served.\n\n\nFeature flags help stakeholders reduce risk, allowing them to do controlled\ntesting of features and separate feature delivery from customer launch.\n\n\n## Benefits of feature flags\n\nThe following are benefits of GitLab's feature flags.\n\n- **Lower risk.** Feature flags prevent unscheduled outages, control your\naudience in a fine-grained fashion, and can be optionally used in\nconjunction with canary deployments.\n\n- **Ease of use.** Feature flags have simple configurability and\ninstrumentation, support user lists, and offer built-in service.\n\n- **Language agnostic.** Our feature flag implementation supports all of the\nmain programming languages.\n\n- **Better compliance and audit capabilities.** The GitLab platform\nautomatically records all feature flags actions.\n\n\n## Tutorial requirements\n\nThis is what you need for this tutorial:\n\n1. A GitLab account on gitlab.com SaaS\n\n2. Flux CLI installed on your local desktop (on my Mac, I installed it by\nexecuting `brew install fluxcd/tap/flux`)\n\n3. A running Kubernetes cluster, i.e. a GKE cluster with 3 e2-medium nodes\n\n4. `kubectl` connectivity to your Kubernetes cluster from a local Terminal\nwindow on your desktop\n\n\n## About this feature flag tutorial\n\nThis tutorial is based on a fictitious application, which is a simplified\ninventory system. The goal of this tutorial is to show you how to create,\nconfigure, and implement a feature flag using GitLab.\n\n\n**Note:** This tutorial is for learning purposes and not meant to deploy a\nproduction-ready architecture. Also, to keep the number of steps low, masked\nvariables and sealed secrets are not being used throughout this tutorial.\n\n\n## Flux and the GitLab agent for Kubernetes\n\nHere is how to install Flux and GitLab agent for Kubernetes.\n\n- Log on to your GitLab workspace.\n\n- Create a personal access token (PAT) from your GitLab account by\nnavigating to **User settings > Preferences > Access tokens**. In the\n**Personal Access Tokens** section, click on the **Add new token** button on\nthe righthand side of the section. For **Token name**, enter `pat-for-flux`.\nLeave the expiration date with its default (it should be 30 days from its\ncreation) and select the **API** scope for your **PAT**. Click on the\n**Create personal access token** button to create your PAT. Copy and save\nthe value of your **PAT**; you will need it at a later step.\n\n\n![create-pat](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-pat.png){:\n.shadow.medium.center}\n\nCreating a personal access token\n\n{: .note.text-center}\n\n\n- Head back to your GitLab workspace main page.\n\n- Create a group named “hn” by clicking the button **New group** (or **New\nsubgroup** if you are creating this group inside an existing group) on the\ntop right hand side of your screen, and then clicking on the **Create\ngroup** tile. Enter \"hn\" for your **Group name** and click on the **Create\ngroup** button to create it. Leave the rest of the fields with their\ndefaults.\n\n\n![create-group-hn](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-group-hn.png){:\n.shadow.medium.center}\n\nCreating group \"hn\"\n\n{: .note.text-center}\n\n\n- Inside group “hn”, create project “flux-config” by clicking the **New\nproject** on the top righthand side of your screen and then clicking on the\n**Create blank project** tile.\n\n\n![create-proj-flux-config](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-proj-flux-config.png){:\n.shadow.medium.center}\n\nCreating project \"flux-config\"\n\n{: .note.text-center}\n\n\n- From the Terminal window with `kubectl` access to your Kubernetes cluster,\nexport your **PAT** by entering the following command:\n\n\n> export GITLAB_TOKEN=`\u003Creplace with your PAT value>`\n\n\n- From the Terminal window with `kubectl` access to your Kubernetes cluster,\nbootstrap Flux by executing the following command:\n\n\n**Note:** Make sure to replace `\u003Cyour path>` with whatever precedes your\ngroup “hn”. For example, it could be `--owner=tech-marketing/sandbox/hn`, or\nif your group “hn” is at the very top level of your GitLab workspace, it\nwould be `--owner=hn`.\n\n\n```\n\nflux bootstrap gitlab \\\n  --owner=\u003Cyour path>/hn \\\n  --repository=flux-config \\\n  --branch=main \\\n  --path=clusters/my-cluster \\\n  --deploy-token-auth\n```\n\n\n![flux-bootstrap-output](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/flux-bootstrap-output.png){:\n.shadow.medium.center.}\n\nFlux bootstrap output\n\n{: .note.text-center}\n\n\nThe “flux-config” project should now contain new directories and files as\nshown below:\n\n\n![flux-config-post-bootstrap](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/flux-config-post-bootstrap.png){:\n.shadow.medium.center}\n\nProject flux-config post flux bootstrap process\n\n{: .note.text-center}\n\n\n- Head over to project **hn/flux-config** and create file\n“.gitlab/agents/k8s-agent/config.yaml” by clicking on the **+** sign next to\nthe “flux-config” and selecting **New file**. Paste the following into it\nthe new file:\n\n\n**Note:** Make sure to replace `\u003Cyour path>` with whatever precedes your\ngroup “hn”. For example, it could be `- id: tech-marketing/sandbox/hn` or if\nyour group “hn” is at the very top level of your GitLab workspace, it would\nbe `- id: hn`.\n\n\n```\n\nci_access:\n  groups:\n    - id: \u003Cyour path>/hn\n```\n\n\nCommit this file to main by clicking on the **Commit changes** button and\nensuring that the target branch is “main”.\n\n\n![create-config-yaml](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-config-yaml.png){:\n.shadow.medium.center}\n\nCreating the GitLab agent for Kubernetes configuration manifest\n\n{: .note.text-center}\n\n\n- Head to **Operate > Kubernetes clusters** and register the agent by\nclicking the **Connect a cluster** button.\n\n\n![register-agent](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/register-agent.png){:\n.shadow.medium.center}\n\nRegistering the GitLab agent for Kubernetes\n\n{: .note.text-center}\n\n\n- On the “Connect a Kubernetes cluster” dialog, click on the popdown list\nand select agent “k8s-agent”. Click on the **Register** button. The dialog\nwill refresh and show the **Agent access token**. Copy and save the **Agent\naccess token**; you will need it at a later step. Close the dialog by\nclicking on the **Close** button.\n\n\n![agent-access-token-dialog](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/agent-access-token-dialog.png){:\n.shadow.medium.center}\n\nThe agent access token to save\n\n{: .note.text-center}\n\n\nAt this moment, you will see the agent listed and its Connection status will\nbe “Never connected”.\n\n\n![agent-not-connected](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/agent-not-connected.png){:\n.shadow.medium.center}\n\nAgent registered but not connected yet\n\n{: .note.text-center}\n\n\n-  Head to **flux-config/clusters/my-cluster** directory and create a file\nnamed “namespace-gitlab.yaml” and paste the following into it:\n\n\n```\n\napiVersion: v1\n\nkind: Namespace\n\nmetadata:\n  name: gitlab\n```\n\n\n![gitlab-namespace-manifest](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/gitlab-namespace-manifest.png){:\n.shadow.medium.center}\n\nManifest for the gitlab namespace\n\n{: .note.text-center}\n\n\nCommit this file to main by clicking on the **Commit changes** button and\nensuring that the target branch is “main”.\n\n\n```\n\nNote: You can check that the namespace was created in your cluster by\nexecuting this command from a Terminal:\n\n\nkubectl get ns\n\n```\n\n\n![gitlab-ns-created](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/gitlab-ns-created.png){:\n.shadow.medium.center}\n\nFlux created gitlab namespace\n\n{: .note.text-center}\n\n\n- Before we have Flux deploy the GitLab agent for Kubernetes to your\ncluster, we need to create a secret, containing the **Agent access token**\nyou saved earlier, in your cluster. Create a file named “secret.yaml” in\nyour local desktop, paste the following into it and then save it:\n\n\n**Note:** Make sure to replace `\u003Cyour-agent-access-token-here>` with your\n**Agent access token** you saved earlier.\n\n\n```\n\napiVersion: v1\n\nkind: Secret\n\nmetadata:\n  name: gitlab-agent-token-initial\ntype: Opaque\n\nstringData:\n  values.yaml: |-\n    config:\n      token: \"\u003Cyour-agent-access-token-here>\"\n```\n\n\n![agent-token-secret](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/agent-token-secret.png){:\n.shadow.medium.center.}\n\nManifest for agent token secret created on local desktop\n\n{: .note.text-center}\n\n\n- Create the secret in your cluster by executing the following command from\na Terminal:\n\n\n> kubectl apply -f secret.yaml -n gitlab\n\n\n```\n\nNote: You can check that the secret was created in your cluster by executing\nthis command from a Terminal:\n\n\nkubectl get secrets -n gitlab\n\n```\n\n\n![apply-agent-token-secret](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/apply-agent-token-secret.png){:\n.shadow.medium.center}\n\nApplying the agent token secret to the Kubernetes cluster\n\n{: .note.text-center}\n\n\n- Now let’s use the Flux Helm Controller to deploy the GitLab agent for\nKubernetes to your cluster. Head to **flux-config/clusters/my-cluster**\ndirectory and create a file named “agentk.yaml” and paste the following into\nit:\n\n\n```\n\n---\n\napiVersion: source.toolkit.fluxcd.io/v1beta2\n\nkind: HelmRepository\n\nmetadata:\n  labels:\n    app.kubernetes.io/component: agentk\n    app.kubernetes.io/created-by: gitlab\n    app.kubernetes.io/name: agentk\n    app.kubernetes.io/part-of: gitlab\n  name: gitlab-agent\n  namespace: gitlab\nspec:\n  interval: 1h0m0s\n  url: https://charts.gitlab.io\n---\n\napiVersion: helm.toolkit.fluxcd.io/v2beta1\n\nkind: HelmRelease\n\nmetadata:\n  name: gitlab-agent\n  namespace: gitlab\nspec:\n  chart:\n    spec:\n      chart: gitlab-agent\n      sourceRef:\n        kind: HelmRepository\n        name: gitlab-agent\n        namespace: gitlab\n  interval: 1h0m0s\n  values:\n    replicas: 1\n    config:\n      kasAddress: \"wss://kas.gitlab.com\"  \n  valuesFrom:\n    - kind: Secret\n      name: gitlab-agent-token-initial\n      valuesKey: values.yaml\n```\n\n\n![create-agentk-manifest](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-agentk-manifest.png){:\n.shadow.medium.center}\n\nCreating the manifest for the GitLab agent for Kubernetes\n\n{: .note.text-center}\n\n\nCommit this file to main by clicking on the **Commit changes** button and\nensuring that the target branch is “main”.\n\n\n```\n\nNote: In a few seconds, you can check that the GitLab agent for Kubernetes\nwas created in your cluster by executing this command from a Terminal (the\npod name should start with “gitlab-agent”):\n\n\nkubectl get pods -n gitlab\n\n```\n\n![agentk-pod-up](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/agentk-pod-up.png){:\n.shadow.medium.center}\n\nAgentk running in the Kubernetes cluster\n\n{: .note.text-center}\n\n\n## Creating an instance of MySQL database in your cluster via Flux\n\n- Using the breadcrumb at the top of your window, head to group “hn” and\ncreate a new project by clicking on the **New project** button. On the\n**Create new project** window, click on the **Import project** tile.\n\n- At the **Import project** window, click on the **Repository by URL**\nbutton. The window will display fields to enter the URL of the repository\nyou would like to import. In the text field **Git repository URL**, enter\nthe following:\n\n\n>\n[https://gitlab.com/tech-marketing/sandbox/mysql.git](https://gitlab.com/tech-marketing/sandbox/mysql.git)\n\n\nLeave the rest of the fields with their defaults.\n\n\n![import-mysql-proj](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/import-mysql-proj.png){:\n.shadow.medium.center}\n\nImporting mysql project into group \"hn\"\n\n{: .note.text-center}\n\n\n- Click on the **Create project** button at the bottom of the screen. You\nwill see an \"Importing in progress\" message temporarily on your screen.\n\n- Now we need to create a deploy token for this project so that Flux can\ninteract with it. While in project “mysql”, select **Settings > Repository**\nand scroll down to the **Deploy tokens** section. Click on the **Expand**\nbutton to the right of the **Deploy tokens** section. Then click on the\n**Add token** button, which will expand the section to include fields to\nstart entering information for the deploy token to be created.\n\n- Give the deploy token the name “mysql-flux-deploy-token” and check the\ncheckbox **read_repository** for it. Then click on the button **Create\ndeploy token** to create the token.\n\n\n![create-mysql-deploy-token](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-mysql-deploy-token.png){:\n.shadow.medium.center}\n\nCreating the deploy token for \"mysql\" project for Flux to interact with it\n\n{: .note.text-center}\n\n\nCopy and save the username and password for the newly created deploy token;\nyou will need them at a later step.\n\n\n![mysql-deploy-token-created](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/mysql-deploy-token-created.png){:\n.shadow.medium.center}\n\nCreating the deploy token for \"mysql\" project for Flux to interact with it\n\n{: .note.text-center}\n\n\n-  From a Terminal, execute the following command to create a secret in your\ncluster for the deploy token you just created:\n\n\n**Note:** Make sure to replace `\u003Cyour path>` with the missing partial path\nto the project “mysql”, \u003Cyour-deploy-token-username> with the deploy token\nusername you saved earlier, and the \u003Cyour-deploy-token-password> with the\ndeploy token password you saved earlier.\n\n\n```\n\nflux create secret git mysql-flux-deploy-authentication \\\n         --url=https://gitlab.com/\u003Cyour path>/hn/mysql \\\n         --namespace=default \\\n         --username=\u003Cyour-deploy-token-username> \\\n         --password=\u003Cyour-deploy-token-password>\n```\n\n\n```\n\nNote: You can check that the secret was created in your cluster by executing\nthis command from a Terminal:\n\n\nkubectl -n default get secrets mysql-flux-deploy-authentication\n\n```\n\n\n![mysql-secret-created](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/mysql-secret-created.png){:\n.shadow.medium.center}\n\nCreating secret for the deploy token for \"mysql\" project in the Kubernetes\ncluster\n\n{: .note.text-center}\n\n\n- Head back to project “hn/flux-config” and open the Web IDE from it.\n\n\n![open-web-ide](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/open-web-ide.png){:\n.shadow.medium.center}\n\nSelecting Web IDE from the dropdown menu\n\n{: .note.text-center}\n\n\n- From inside the Web IDE, navigate to directory \"clusters/my-cluster\".\n\n\n![goto-clusters-mycluster](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/goto-clusters-mycluster.png){:\n.shadow.medium.center}\n\nNavigate to directory \"clusters/my-cluster\" in the Web IDE\n\n{: .note.text-center}\n\n\n- Inside “clusters/my-cluster” directory, create file\n“mysql-manifests-source.yaml” and paste the following text into it:\n\n\n**Note:** Replace `\u003Cyour path>` with the missing partial path to the project\n“mysql”\n\n\n```\n\napiVersion: source.toolkit.fluxcd.io/v1beta2\n\nkind: GitRepository\n\nmetadata:\n  name: mysql\n  namespace: default\nspec:\n  interval: 1m0s\n  ref:\n    branch: main\n  secretRef:\n    name: mysql-flux-deploy-authentication\n  url: https://gitlab.com/\u003Cyour path>/hn/mysql\n```\n\n\n![create-mysql-source-manifest](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-mysql-source-manifest.png){:\n.shadow.medium.center}\n\nCreating mysql-manifests-source.yaml file in the Web IDE\n\n{: .note.text-center}\n\n\n- Still in the Web IDE, inside “clusters/my-cluster” directory, create file\n“mysql-manifests-kustomization.yaml” and paste the following text into it:\n\n\n```\n\napiVersion: kustomize.toolkit.fluxcd.io/v1beta2\n\nkind: Kustomization\n\nmetadata:\n  name: mysql-source-kustomization\n  namespace: default\nspec:\n  interval: 1m0s\n  path: ./\n  prune: true\n  sourceRef:\n    kind: GitRepository\n    name: mysql\n    namespace: default\n  targetNamespace: default\n```\n\n\n![create-mysql-kustomization-manifest](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-mysql-kustomization-manifest.png){:\n.shadow.medium.center}\n\nCreating mysql-manifests-kustomization.yaml file in the Web IDE\n\n{: .note.text-center}\n\n\n- From the Web IDE, commit both files to the main branch by clicking on the\n**Source Control** icon on the left vertical menu, pressing the **Commit to\nmain** button.\n\n\n![commit-to-main](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/commit-to-main.png){:\n.shadow.medium.center}\n\nClicking on the Source Control icon and committing to main in the Web IDE\n\n{: .note.text-center}\n\n\nThen press the **Continue** button to confirm that you want to commit your\nchanges to the default branch:\n\n\n![commit-to-main-continue](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/commit-to-main-continue.png){:\n.shadow.medium.center}\n\nClicking on the Source Control icon and committing to main in the Web IDE\n\n{: .note.text-center}\n\n\n- Flux will deploy MySQL to your Kubernetes cluster. You can close the Web\nIDE browser tab at this point.\n\n\n```\n\nNote: You can check that the GitLab agent for Kubernetes was created in your\ncluster by executing this command from a Terminal:\n\n\nkubectl get pods -l app=mysql\n\n\nYou can check the persistent volume by executing this command from a\nTerminal:\n\n\nkubectl describe pvc mysql-pv-claim\n\n```\n\n\n![mysql-pod-and-pv-up](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/mysql-pod-and-pv-up.png){:\n.shadow.center}\n\nVerifying that mysql pod and its associated persitent volume claim are up\nand ready\n\n{: .note.text-center}\n\n\n- Now that the MySQL pod is up and running, we need to create a database,\ntables, and indexes in it and also populate some of the tables with dummy\ndata for the inventory system. Using the breadcrumb at the top of your\nwindow, head over to the “mysql” project and select **Build > Pipelines**\nfrom the left vertical navigation menu.\n\n\n![head-to-mysql-build-pipelines](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/head-to-mysql-build-pipelines.png){:\n.shadow.medium.center}\n\nHead to \"mysql\" project and select **Build > Pipelines** from the left\nvertical navigation menu\n\n{: .note.text-center}\n\n\n- Click on the **Run pipeline** button on the top right side of the\n**Pipelines** window. This will put you on the **Run pipeline** window.\nClick on the **Run pipeline** button on the bottom left of the **Run\npipeline** window leaving the rest of the fields with its defaults.\n\n\n![run-pipeline-button](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/run-pipeline-button.png){:\n.shadow.medium.center}\n\nClicking on the **Run pipeline** button to run the project \"mysql\" pipeline\n\n{: .note.text-center}\n\n\n- At this point you will see the pipeline stage and jobs. There are two jobs\nunder the **Build** stage: **create_and_load_db** and **clear_db**.\n\n\n![mysql-pipeline](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/mysql-pipeline.png){:\n.shadow.medium.center}\n\nThe \"mysql\" pipeline and its two manual jobs\n\n{: .note.text-center}\n\n\n- Click on the Play button (the right solid arrow) next to the\n**create_and_load_db** job name. This job will create a **product** table\nand a **users** table and populate them with dummy data. It will also create\ntables and indexes needed for storing all the session-related information as\nusers log in and log out from the inventory system.\n\n\n**Note:** The **clear_db** job should only be used if you’d like to erase\nall of the database resources created by the **create_and_load_db** job. The\n**clear_db** should only be used AFTER a failed run of the\n**create_and_load_db** job.\n\n\nNow that we have the database ready to go, let’s set up the project that we\nwill use for the creation of the feature flags.\n\n\n## Creating and importing projects\n\n- Head back to group “hn” and inside of it, create a cluster management\nproject (you can call it “cluster-management”) at the same level as the\nproject you imported above. You can view this [instructional\nvideo](https://www.youtube.com/watch?v=QRR3WuwnxXE&t=200s) (up to minute\n6:09) to see how to do this. While applying the steps in the video for this\ntutorial, adjust the variables values from the video to this post as\ndescribed in the following notes:\n\n\n**Note 1:** Make sure to create and set the KUBE_CONTEXT and KUBE_NAMESPACE\nvariable in group “hn” and to these values:\n\n\n| variable | value |\n\n| ---          | ---      |\n\n| KUBE_CONTEXT | `\u003Cyour path>`/hn/flux-config:k8s-agent |\n\n| KUBE_NAMESPACE | my-apps |\n\n\nFor example, in my case `\u003Cyour path>` was\n“tech-marketing/sandbox/hn/flux-config:k8s-agent”. In your case, it will be\ndifferent. If `\u003Cyour path>` is at the root of your GitLab workspace, then it\nwould be empty so the value of KUBE_CONTEXT would be\n“hn/flux-config:k8s-agent”.\n\n\n![add-var-KUBE_CONTEXT](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-var-KUBE_CONTEXT.png){:\n.shadow.medium.center}\n\nAdding variable KUBE_CONTEXT in group \"hn\"\n\n{: .note.text-center}\n\n\n![add-var-KUBE_NAMESPACE](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-var-KUBE_NAMESPACE.png){:\n.shadow.medium.center}\n\nAdding variable KUBE_NAMESPACE in group \"hn\"\n\n{: .note.text-center}\n\n\n**Note 2:** As an FYI, when uncommenting the GitLab managed apps in the\n“helmfile.yaml” file, there will not be one for Prometheus. So, you will\nonly uncomment the lines for ingress and cert-manager.\n\n\n![uncomment-ingress-and-cert-manager](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/uncomment-ingress-and-cert-manager.png){:\n.shadow.medium.center}\n\nUncommenting lines for ingress and cert-manager in file \"helmfile.yaml\"\n\n{: .note.text-center}\n\n\n**Note 3:** When the pipeline for project “cluster-management” runs, you\nwill notice that the job “sync” is a manual job. You will need to click on\nits **Play** (right arrow next to its name) button to run it. Wait until the\n“sync” job completes successfully before continuing.\n\n\n![click-play-on-sync-job](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/click-play-on-sync-job.png){:\n.shadow.medium.center}\n\nJob \"sync\" is manual so you need to press on the **Play** button next to its\nname\n\n{: .note.text-center}\n\n\n**Note 4:** Once the pipeline finishes, for your convenience, here is the\ncommand you need to run from a Terminal window to get the **external IP**\naddress of your cluster:\n\n\n```\n\nkubectl --namespace gitlab-managed-apps get services -o wide -w\ningress-ingress-nginx-controller\n\n```\n\n\n![getting-external-ip-address](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/getting-external-ip-address.png){:\n.shadow.medium.center}\n\nRunning `kubectl` command to get the ingress IP address to the cluster\n\n{: .note.text-center}\n\n\nCreate and set a variable `KUBE_INGRESS_BASE_DOMAIN` in group “hn” and set\nit to the **external IP** address of your cluster and append the suffix\n“.nip.io” to it.\n\n\n![add-var-KUBE_INGRESS_BASE_DOMAIN](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-var-KUBE_INGRESS_BASE_DOMAIN.png){:\n.shadow.medium.center}\n\nAddding variable KUBE_INGRESS_BASE_DOMAIN in group \"hn\"\n\n{: .note.text-center}\n\n\n- Inside group “hn”, create a new project. Click on the **New project**\nbutton. On the **Create new project** window, click on the **Import\nproject** tile and then click on the **Repository by URL** button.\n\n- This will expand the window and show fields to enter the URL of the\nrepository you would like to import. In the field **Git repository URL**,\nenter the following:\n\n\n>\n[https://gitlab.com/tech-marketing/sandbox/prodmgr.git](https://gitlab.com/tech-marketing/sandbox/prodmgr.git)\n\n\nLeave the rest of the fields with their defaults.\n\n\n![import-prodmgr-proj](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/import-prodmgr-proj.png){:\n.shadow.medium.center}\n\nImporting project \"prodmgr\" into group \"hn\"\n\n{: .note.text-center}\n\n\n- Click on the **Create project** button at the bottom of the screen. You\nwill see an **Importing in progress** message temporarily on your screen.\n\n- In project “prodmgr”, create a pipeline file and make sure to name it\n“.gitlab-ci.yml”. Paste the following code block into the empty file:\n\n\n```\n\ninclude:\n  template: Auto-DevOps.gitlab-ci.yml\n\nvariables:\n  K8S_SECRET_TF_VAR_dbusername: \"sasha\"\n  K8S_SECRET_TF_VAR_dbpassword: \"password\"\n  TEST_DISABLED: \"true\"\n  CODE_QUALITY_DISABLED: \"true\"\n  LICENSE_MANAGEMENT_DISABLED: \"true\"\n  BROWSER_PERFORMANCE_DISABLED: \"true\"\n  LOAD_PERFORMANCE_DISABLED: \"true\"\n  SAST_DISABLED: \"true\"\n  SECRET_DETECTION_DISABLED: \"true\"\n  DEPENDENCY_SCANNING_DISABLED: \"true\"\n  CONTAINER_SCANNING_DISABLED: \"true\"\n  DAST_DISABLED: \"true\"\n  REVIEW_DISABLED: \"true\"\n  CODE_INTELLIGENCE_DISABLED: \"true\"\n  CLUSTER_IMAGE_SCANNING_DISABLED: \"true\"\n  POSTGRES_ENABLED: \"false\"\n  STAGING_ENABLED: \"true\"\n  INCREMENTAL_ROLLOUT_MODE: \"manual\"\n```\n\n\nClick on the **Commit changes** button ensuring that the **Target branch**\nis main.\n\n\n![prodmgr-proj-pipeline](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/prodmgr-proj-pipeline.png){:\n.shadow.medium.center}\n\nCreating an Auto-DevOps-based pipeline for project \"prodmgr\"\n\n{: .note.text-center}\n\n\n- The previous step builds the application and deploys it to the staging\nenvironment. Once deployed to staging, head to **Build > Pipelines** and\nclick on the most recently executed pipeline (should be the first one in the\nlist). Click on the pipeline to display it and then deploy the application\nto production by clicking on “rollout 100%” job.\n\n\n![rollout-to-prod](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/rollout-to-prod.png){:\n.shadow.medium.center}\n\nTo deploy the application to production, click on the **rollout 100%** Play\nbutton\n\n{: .note.text-center}\n\n\nAt this point, you have a running application in the staging and production\nenvironments in your Kubernetes cluster. Let’s start creating a feature\nflag.\n\n\n## Creating a new feature flag\n\n-  In project “prodmgr”, select **Deploy > Feature flags** from your left\nvertical navigation menu.\n\n\n### Creating a user list\n\n- Click on the link **View user lists** on the top right hand side of your\nscreen.\n\n- Click on the **New user list** button on the top right hand side of your\nscreen.\n\n- In the **Name** field of the user list, enter\n“prods-in-alphabetical-order-userlist” and then click on the **Create**\nbutton.\n\n\n![create-ff-userlist](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/create-ff-userlist.png){:\n.shadow.medium.center.}\n\nCreating user list named \"prods-in-alphabetical-order-userlist”\n\n{: .note.text-center}\n\n\n- On the next screen, click on the **Add Users** button on the top right\nhand side of your screen.\n\n- In the **User IDs** text field, enter the following two email addresses\nand then click on the **Add** button:\n\n\n> michael@cfl.rr.com,mary@cfl.rr.com\n\n\n![add-users-to-list](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-users-to-list.png){:\n.shadow.medium.center}\n\nAdding users to user list \"prods-in-alphabetical-order-userlist”\n\n{: .note.text-center}\n\n\n- Head back to the Feature flags window by selecting **Deploy > Feature\nflags** from your left vertical navigation menu.\n\n\n### Creating the flag\n\n- Click on the **New feature flag** button on the top right hand side of\nyour screen.\n\n- In the **New feature flag** window, enter\n“prods-in-alphabetical-order-ff”.\n\n\n### Specifying the strategy for the production environment\n\nIn the **Strategies** section of the **New feature flag** window, there\nshould already be sub-sections for **Type** and **Environments**.\n\n- For **Type**, select **Percent rollout** from the dropdown menu.\n\n- For **Percentage**, enter **50** in the field.\n\n- For **Based on**, ensure that **Available ID** is selected from the\npopdown menu.\n\n- For **Environments**, click on the **+** sign and select the\n**production** environment.\n\n\n### Specifying the strategy for the staging environment\n\n- Click on the **Add strategy** button on the right hand side of the\n**Strategies** section. A new sub-section for another strategy will appear.\n\n- For **Type**, select **User List** from the dropdown menu.\n\n- For **User List**, select the user list\n**prods-in-alphabetical-order-userlist**.\n\n- For **Environments**, click on the **+** sign and select the **staging**\nenvironment.\n\n- Click on **Create feature flag** button at the bottom of your screen to\ncomplete the creation of the feature flag.\n\n\n![ff-and-strats-def](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/ff-and-strats-def.png){:\n.shadow.medium.center}\n\nDefining the feature flag with its strategies for strating and production\nenvironments\n\n{: .note.text-center}\n\n\n## Sharing feature flag configuration information with developers\n\nIn order for developers to instrument their code for this feature flag, you\nneed to share with them the following information:\n\n- On the **Feature flags** window, click on the **Configure** button on the\ntop right hand side of your screen.\n\n- Copy and save the values of **API URL** (URL where the client application\nconnects to get a list of feature flags) and **Instance ID** (unique token\nthat authorizes the retrieval of the feature flags). These are the two\nvalues that you will need for feature flag instrumentation.\n\n\n![ff-api-url-and-instance-id](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/ff-api-url-and-instance-id.png){:\n.shadow.medium.center}\n\nCopy and save the values for the feature flag API URL and Instance ID\n\n{: .note.text-center}\n\n\n- Head over to **Settings > CI/CD** and scroll down to the **Variables**\nsection and click on its **Expand** button. Add the following two variables\nto your project:\n\n\n| Variable Key | Variable Value | Variable Type | Environment Scope | Flag -\nProtect variable | Flag - Mask variable\n\n| ----------- | ----------- | ----------- |----------- | ----------- |\n----------- |\n\n| K8S_SECRET_UNLEASH_URL | \\\u003Csaved **API URL** value\\> | Variable | All\n(default) | unchecked | unchecked\n\n| K8S_SECRET_UNLEASH_INSTANCE_ID | \\\u003Csaved **Instance ID** value\\> |\nVariable | All (default) | unchecked | unchecked\n\n\n![add-var-K8S_SECRET_UNLEASH_URL](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-var-K8S_SECRET_UNLEASH_URL.png){:\n.shadow.medium.center}\n\nAdding variable K8S_SECRET_UNLEASH_URL to project \"prodmgr\"\n\n{: .note.text-center}\n\n\n![add-var-K8S_SECRET_UNLEASH_INSTANCE_ID](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/add-var-K8S_SECRET_UNLEASH_INSTANCE_ID.png){:\n.shadow.medium.center}\n\nAdding variable K8S_SECRET_UNLEASH_INSTANCE_ID to project \"prodmgr\"\n\n{: .note.text-center}\n\n\nThese two variables contain values that will be passed to your application\n(via the K8S_SECRET_ keyword) so that it can make use of the feature flags\ndefined and managed by GitLab.\n\n\nIn order for your application to be able to use feature flags, you need to\ninstrument your application with our Feature Flags framework. Let's see how\nyou do this in the sample Java application.\n\n\n## Instrumenting the code\n\nIn this example, we are using the Java client for Unleash but if you’re\nusing a different programming language then you need to use the client\nlibrary for your language. To get all the supported languages, refer to the\n[Unleash documentation](https://docs.getunleash.io/reference/sdks) or\n[Unleash open source\nproject](https://github.com/Unleash/unleash#unleash-sdks).\n\n\n### Instrumenting Java class files\n\n- In project “prodmgr”, navigate to the directory\n`src/main/java/csaa/jspring/ProductManager`.\n\n- Click on the file name “AppController.java” to view its contents and then\nclick on the Edit button to enter edit mode.\n\n- You will see a few code blocks that have been commented out and are\npreceded by the line:\n\n\n> // Uncomment block below to instrument Feature Flag\n\n\nUncomment all the code blocks under each of the lines indicated above.\n\n\n![java-file-with-uncommented-lines](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/java-file-with-uncommented-lines.png){:\n.shadow.medium.center}\n\nPartial view of AppController.java file with uncommented code blocks\n\n{: .note.text-center}\n\n\n- Commit the changes to the main branch.\n\n- The commit starts a pipeline that deploys the application to the staging\nenvironment. Head to **Build > Pipelines** and click on the most recently\nexecuted pipeline (should be the first one in the list). Click on the\npipeline to display it and wait until the **staging** job finishes. Then\ndeploy the application to production by clicking on “rollout 100%” job.\n\n\nNow that the application is running in the staging and production\nenvironments, let’s see the feature flag in action.\n\n\n## Feature flag in action\n\nNow let's check how the feature flag is working.\n\n### Checking the feature flag in the staging environment\n\n- In project “prodmgr”, click on **Operate > Environments** to see the list\nof all environments. Then click on the \"Open live environment\" button for\nthe staging environment.\n\n- A new browser tab will appear and will display a login screen. If your\nbrowser complains about the connection being insecure, accept the risk and\nopen the browser tab.\n\n- Remember that the feature flag strategy for staging is based on the user\nlist containing michael and mary in it. Let’s try logging in as each of\nthem.\n\n- Enter credentials michael@cfl.rr.com with password p33sw0rd. Verify that\nMichael gets a product list sorted in alphabetical order. Log out and close\nthe browser tab to ensure that his session closes.\n\n\n![michael-gets-ff](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/michael-gets-ff.png){:\n.shadow.medium.center}\n\nMichael gets the feature flag that orders the list of product names in\nalphabetical order\n\n{: .note.text-center}\n\n\n- From the Environments window, click on the \"Open live environment\" button\nfor the staging environment. Enter credentials \"mary@cfl.rr.com\" with\npassword \"p33sw0rd\". Verify that mary gets a product list sorted in\nalphabetical order. Log out and close the browser tab to ensure that her\nsession closes.\n\n- From the Environments window, click on the \"Open live environment\" button\nfor the staging environment. This time, enter credentials for\n\"thomas@gmail.com\" with password \"p33sw0rd\". Verify that thomas does **not**\nget a product list sorted in alphabetical order. Log out and close the\nbrowser tab to ensure that his session closes.\n\n\n![thomas-does-not-get-the-ff](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/thomas-does-not-get-the-ff.png){:\n.shadow.medium.center}\n\nThomas does not get the feature flag because the product names are not\nordered in alphabetical order\n\n{: .note.text-center}\n\n\nThe steps above demonstrate that the feature flag strategy for staging\nsuccessfully worked.\n\n\n### Checking the feature flag in the production environment\n\n- Click on **Operate > Environments** to see the list of all environments.\nThen click on the \"Open live environment\" button for the production\nenvironment.\n\n- A new browser tab will appear and will display a login screen. If your\nbrowser complains about the connection being insecure, accept the risk and\nopen the browser tab.\n\n- Remember that the strategy in production is that the feature will be\nserved to 50% of the users. Try logging into the web application as each of\nthe following users keeping track of who gets the list of products sorted in\nalphabetical order by name and who does not:\n\n\n**Note:** Remember to click on the \"Open live environment\" button for the\n**production** environment. Once you log out from each user, remember to\n**close** the browser tab to ensure that the session closes.\n\n\n| Username | Password\n\n| ----------- | ----------- |\n\n| peter@gmail.com | pa33w0rd\n\n| magic@cfl.rr.com | pa33w0rd\n\n| michael@cfl.rr.com | pa33w0rd\n\n| henry@gmail.com | pa33w0rd\n\n| mary@cfl.rr.com | pa33w0rd\n\n| thomas@gmail.com | pa33w0rd\n\n\nYour final count should consist of three users being served the feature and\nthree not, matching the strategy that was set for the production\nenvironment.\n\n\nAs changes are made to feature flags, you can track them from the audit\nevents window.\n\n\n## Auditing feature flag changes\n\n**Note:** A Premium GitLab subscription is needed for viewing Audit events.\n\n\n- In project “prodmgr”, select **Secure > Audit events** from the left\nvertical navigation menu.\n\n- This displays all the events that have occurred in GitLab for the last\nthirty days. You will see that events related to updates to feature flags\nare listed.\n\n\n![audit-events-list](https://about.gitlab.com/images/blogimages/feature-flags-tutorial/audit-events-list.png){:\n.shadow.medium.center}\n\nAudit events is an auditable list of actions that have been taken againt\nresources\n\n{: .note.text-center}\n\n\nThis auditing allows you to identify when and who made changes to feature\nflags. It can also help preempt out-of-compliance scenarios and streamline\naudits to avoid penalties, providing an opportunity to optimize cost, and\nlower risk of unscheduled production outages.\n\n\nNow you know how to create and use feature flags to lower your deployment\nrisk.\n\n\nPhoto by \u003Ca\nhref=\"https://unsplash.com/@liamdesic?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\">Liam\nDesic\u003C/a> on \u003Ca\nhref=\"https://unsplash.com/photos/acKSt3THWKA?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText\">Unsplash\u003C/a>\n","devsecops",[23,24,25],"CI/CD","features","DevSecOps",{"slug":27,"featured":6,"template":28},"eliminate-risk-with-feature-flags-tutorial","BlogPost","content:en-us:blog:eliminate-risk-with-feature-flags-tutorial.yml","yaml","Eliminate Risk With Feature Flags Tutorial","content","en-us/blog/eliminate-risk-with-feature-flags-tutorial.yml","en-us/blog/eliminate-risk-with-feature-flags-tutorial","yml",{"_path":37,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"data":39,"_id":459,"_type":30,"title":460,"_source":32,"_file":461,"_stem":462,"_extension":35},"/shared/en-us/main-navigation","en-us",{"logo":40,"freeTrial":45,"sales":50,"login":55,"items":60,"search":390,"minimal":421,"duo":440,"pricingDeployment":449},{"config":41},{"href":42,"dataGaName":43,"dataGaLocation":44},"/","gitlab logo","header",{"text":46,"config":47},"Get free trial",{"href":48,"dataGaName":49,"dataGaLocation":44},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":51,"config":52},"Talk to sales",{"href":53,"dataGaName":54,"dataGaLocation":44},"/sales/","sales",{"text":56,"config":57},"Sign in",{"href":58,"dataGaName":59,"dataGaLocation":44},"https://gitlab.com/users/sign_in/","sign in",[61,105,201,206,311,371],{"text":62,"config":63,"cards":65,"footer":88},"Platform",{"dataNavLevelOne":64},"platform",[66,72,80],{"title":62,"description":67,"link":68},"The most comprehensive AI-powered DevSecOps Platform",{"text":69,"config":70},"Explore our Platform",{"href":71,"dataGaName":64,"dataGaLocation":44},"/platform/",{"title":73,"description":74,"link":75},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":76,"config":77},"Meet GitLab Duo",{"href":78,"dataGaName":79,"dataGaLocation":44},"/gitlab-duo/","gitlab duo ai",{"title":81,"description":82,"link":83},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":84,"config":85},"Learn more",{"href":86,"dataGaName":87,"dataGaLocation":44},"/why-gitlab/","why gitlab",{"title":89,"items":90},"Get started with",[91,96,101],{"text":92,"config":93},"Platform Engineering",{"href":94,"dataGaName":95,"dataGaLocation":44},"/solutions/platform-engineering/","platform engineering",{"text":97,"config":98},"Developer Experience",{"href":99,"dataGaName":100,"dataGaLocation":44},"/developer-experience/","Developer experience",{"text":102,"config":103},"MLOps",{"href":104,"dataGaName":102,"dataGaLocation":44},"/topics/devops/the-role-of-ai-in-devops/",{"text":106,"left":107,"config":108,"link":110,"lists":114,"footer":183},"Product",true,{"dataNavLevelOne":109},"solutions",{"text":111,"config":112},"View all Solutions",{"href":113,"dataGaName":109,"dataGaLocation":44},"/solutions/",[115,139,162],{"title":116,"description":117,"link":118,"items":123},"Automation","CI/CD and automation to accelerate deployment",{"config":119},{"icon":120,"href":121,"dataGaName":122,"dataGaLocation":44},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[124,127,131,135],{"text":23,"config":125},{"href":126,"dataGaLocation":44,"dataGaName":23},"/solutions/continuous-integration/",{"text":128,"config":129},"AI-Assisted Development",{"href":78,"dataGaLocation":44,"dataGaName":130},"AI assisted development",{"text":132,"config":133},"Source Code Management",{"href":134,"dataGaLocation":44,"dataGaName":132},"/solutions/source-code-management/",{"text":136,"config":137},"Automated Software Delivery",{"href":121,"dataGaLocation":44,"dataGaName":138},"Automated software delivery",{"title":140,"description":141,"link":142,"items":147},"Security","Deliver code faster without compromising security",{"config":143},{"href":144,"dataGaName":145,"dataGaLocation":44,"icon":146},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[148,152,157],{"text":149,"config":150},"Application Security Testing",{"href":144,"dataGaName":151,"dataGaLocation":44},"Application security testing",{"text":153,"config":154},"Software Supply Chain Security",{"href":155,"dataGaLocation":44,"dataGaName":156},"/solutions/supply-chain/","Software supply chain security",{"text":158,"config":159},"Software Compliance",{"href":160,"dataGaName":161,"dataGaLocation":44},"/solutions/software-compliance/","software compliance",{"title":163,"link":164,"items":169},"Measurement",{"config":165},{"icon":166,"href":167,"dataGaName":168,"dataGaLocation":44},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[170,174,178],{"text":171,"config":172},"Visibility & Measurement",{"href":167,"dataGaLocation":44,"dataGaName":173},"Visibility and Measurement",{"text":175,"config":176},"Value Stream Management",{"href":177,"dataGaLocation":44,"dataGaName":175},"/solutions/value-stream-management/",{"text":179,"config":180},"Analytics & Insights",{"href":181,"dataGaLocation":44,"dataGaName":182},"/solutions/analytics-and-insights/","Analytics and insights",{"title":184,"items":185},"GitLab for",[186,191,196],{"text":187,"config":188},"Enterprise",{"href":189,"dataGaLocation":44,"dataGaName":190},"/enterprise/","enterprise",{"text":192,"config":193},"Small Business",{"href":194,"dataGaLocation":44,"dataGaName":195},"/small-business/","small business",{"text":197,"config":198},"Public Sector",{"href":199,"dataGaLocation":44,"dataGaName":200},"/solutions/public-sector/","public sector",{"text":202,"config":203},"Pricing",{"href":204,"dataGaName":205,"dataGaLocation":44,"dataNavLevelOne":205},"/pricing/","pricing",{"text":207,"config":208,"link":210,"lists":214,"feature":298},"Resources",{"dataNavLevelOne":209},"resources",{"text":211,"config":212},"View all resources",{"href":213,"dataGaName":209,"dataGaLocation":44},"/resources/",[215,248,270],{"title":216,"items":217},"Getting started",[218,223,228,233,238,243],{"text":219,"config":220},"Install",{"href":221,"dataGaName":222,"dataGaLocation":44},"/install/","install",{"text":224,"config":225},"Quick start guides",{"href":226,"dataGaName":227,"dataGaLocation":44},"/get-started/","quick setup checklists",{"text":229,"config":230},"Learn",{"href":231,"dataGaLocation":44,"dataGaName":232},"https://university.gitlab.com/","learn",{"text":234,"config":235},"Product documentation",{"href":236,"dataGaName":237,"dataGaLocation":44},"https://docs.gitlab.com/","product documentation",{"text":239,"config":240},"Best practice videos",{"href":241,"dataGaName":242,"dataGaLocation":44},"/getting-started-videos/","best practice videos",{"text":244,"config":245},"Integrations",{"href":246,"dataGaName":247,"dataGaLocation":44},"/integrations/","integrations",{"title":249,"items":250},"Discover",[251,256,260,265],{"text":252,"config":253},"Customer success stories",{"href":254,"dataGaName":255,"dataGaLocation":44},"/customers/","customer success stories",{"text":257,"config":258},"Blog",{"href":259,"dataGaName":5,"dataGaLocation":44},"/blog/",{"text":261,"config":262},"Remote",{"href":263,"dataGaName":264,"dataGaLocation":44},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":266,"config":267},"TeamOps",{"href":268,"dataGaName":269,"dataGaLocation":44},"/teamops/","teamops",{"title":271,"items":272},"Connect",[273,278,283,288,293],{"text":274,"config":275},"GitLab Services",{"href":276,"dataGaName":277,"dataGaLocation":44},"/services/","services",{"text":279,"config":280},"Community",{"href":281,"dataGaName":282,"dataGaLocation":44},"/community/","community",{"text":284,"config":285},"Forum",{"href":286,"dataGaName":287,"dataGaLocation":44},"https://forum.gitlab.com/","forum",{"text":289,"config":290},"Events",{"href":291,"dataGaName":292,"dataGaLocation":44},"/events/","events",{"text":294,"config":295},"Partners",{"href":296,"dataGaName":297,"dataGaLocation":44},"/partners/","partners",{"backgroundColor":299,"textColor":300,"text":301,"image":302,"link":306},"#2f2a6b","#fff","Insights for the future of software development",{"altText":303,"config":304},"the source promo card",{"src":305},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":307,"config":308},"Read the latest",{"href":309,"dataGaName":310,"dataGaLocation":44},"/the-source/","the source",{"text":312,"config":313,"lists":315},"Company",{"dataNavLevelOne":314},"company",[316],{"items":317},[318,323,329,331,336,341,346,351,356,361,366],{"text":319,"config":320},"About",{"href":321,"dataGaName":322,"dataGaLocation":44},"/company/","about",{"text":324,"config":325,"footerGa":328},"Jobs",{"href":326,"dataGaName":327,"dataGaLocation":44},"/jobs/","jobs",{"dataGaName":327},{"text":289,"config":330},{"href":291,"dataGaName":292,"dataGaLocation":44},{"text":332,"config":333},"Leadership",{"href":334,"dataGaName":335,"dataGaLocation":44},"/company/team/e-group/","leadership",{"text":337,"config":338},"Team",{"href":339,"dataGaName":340,"dataGaLocation":44},"/company/team/","team",{"text":342,"config":343},"Handbook",{"href":344,"dataGaName":345,"dataGaLocation":44},"https://handbook.gitlab.com/","handbook",{"text":347,"config":348},"Investor relations",{"href":349,"dataGaName":350,"dataGaLocation":44},"https://ir.gitlab.com/","investor relations",{"text":352,"config":353},"Trust Center",{"href":354,"dataGaName":355,"dataGaLocation":44},"/security/","trust center",{"text":357,"config":358},"AI Transparency Center",{"href":359,"dataGaName":360,"dataGaLocation":44},"/ai-transparency-center/","ai transparency center",{"text":362,"config":363},"Newsletter",{"href":364,"dataGaName":365,"dataGaLocation":44},"/company/contact/","newsletter",{"text":367,"config":368},"Press",{"href":369,"dataGaName":370,"dataGaLocation":44},"/press/","press",{"text":372,"config":373,"lists":374},"Contact us",{"dataNavLevelOne":314},[375],{"items":376},[377,380,385],{"text":51,"config":378},{"href":53,"dataGaName":379,"dataGaLocation":44},"talk to sales",{"text":381,"config":382},"Support portal",{"href":383,"dataGaName":384,"dataGaLocation":44},"https://support.gitlab.com","support portal",{"text":386,"config":387},"Customer portal",{"href":388,"dataGaName":389,"dataGaLocation":44},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":391,"login":392,"suggestions":399},"Close",{"text":393,"link":394},"To search repositories and projects, login to",{"text":395,"config":396},"gitlab.com",{"href":58,"dataGaName":397,"dataGaLocation":398},"search login","search",{"text":400,"default":401},"Suggestions",[402,404,408,410,414,418],{"text":73,"config":403},{"href":78,"dataGaName":73,"dataGaLocation":398},{"text":405,"config":406},"Code Suggestions (AI)",{"href":407,"dataGaName":405,"dataGaLocation":398},"/solutions/code-suggestions/",{"text":23,"config":409},{"href":126,"dataGaName":23,"dataGaLocation":398},{"text":411,"config":412},"GitLab on AWS",{"href":413,"dataGaName":411,"dataGaLocation":398},"/partners/technology-partners/aws/",{"text":415,"config":416},"GitLab on Google Cloud",{"href":417,"dataGaName":415,"dataGaLocation":398},"/partners/technology-partners/google-cloud-platform/",{"text":419,"config":420},"Why GitLab?",{"href":86,"dataGaName":419,"dataGaLocation":398},{"freeTrial":422,"mobileIcon":427,"desktopIcon":432,"secondaryButton":435},{"text":423,"config":424},"Start free trial",{"href":425,"dataGaName":49,"dataGaLocation":426},"https://gitlab.com/-/trials/new/","nav",{"altText":428,"config":429},"Gitlab Icon",{"src":430,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":428,"config":433},{"src":434,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":436,"config":437},"Get Started",{"href":438,"dataGaName":439,"dataGaLocation":426},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":441,"mobileIcon":445,"desktopIcon":447},{"text":442,"config":443},"Learn more about GitLab Duo",{"href":78,"dataGaName":444,"dataGaLocation":426},"gitlab duo",{"altText":428,"config":446},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":448},{"src":434,"dataGaName":431,"dataGaLocation":426},{"freeTrial":450,"mobileIcon":455,"desktopIcon":457},{"text":451,"config":452},"Back to pricing",{"href":204,"dataGaName":453,"dataGaLocation":426,"icon":454},"back to pricing","GoBack",{"altText":428,"config":456},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":458},{"src":434,"dataGaName":431,"dataGaLocation":426},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":464,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"title":465,"button":466,"image":471,"config":475,"_id":477,"_type":30,"_source":32,"_file":478,"_stem":479,"_extension":35},"/shared/en-us/banner","is now in public beta!",{"text":467,"config":468},"Try the Beta",{"href":469,"dataGaName":470,"dataGaLocation":44},"/gitlab-duo/agent-platform/","duo banner",{"altText":472,"config":473},"GitLab Duo Agent Platform",{"src":474},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":476},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":481,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"data":482,"_id":719,"_type":30,"title":720,"_source":32,"_file":721,"_stem":722,"_extension":35},"/shared/en-us/main-footer",{"text":483,"source":484,"edit":490,"contribute":495,"config":500,"items":505,"minimal":711},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":485,"config":486},"View page source",{"href":487,"dataGaName":488,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":491,"config":492},"Edit this page",{"href":493,"dataGaName":494,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":496,"config":497},"Please contribute",{"href":498,"dataGaName":499,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":501,"facebook":502,"youtube":503,"linkedin":504},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[506,553,604,648,677],{"title":202,"links":507,"subMenu":522},[508,512,517],{"text":509,"config":510},"View plans",{"href":204,"dataGaName":511,"dataGaLocation":489},"view plans",{"text":513,"config":514},"Why Premium?",{"href":515,"dataGaName":516,"dataGaLocation":489},"/pricing/premium/","why premium",{"text":518,"config":519},"Why Ultimate?",{"href":520,"dataGaName":521,"dataGaLocation":489},"/pricing/ultimate/","why ultimate",[523],{"title":524,"links":525},"Contact Us",[526,529,531,533,538,543,548],{"text":527,"config":528},"Contact sales",{"href":53,"dataGaName":54,"dataGaLocation":489},{"text":381,"config":530},{"href":383,"dataGaName":384,"dataGaLocation":489},{"text":386,"config":532},{"href":388,"dataGaName":389,"dataGaLocation":489},{"text":534,"config":535},"Status",{"href":536,"dataGaName":537,"dataGaLocation":489},"https://status.gitlab.com/","status",{"text":539,"config":540},"Terms of use",{"href":541,"dataGaName":542,"dataGaLocation":489},"/terms/","terms of use",{"text":544,"config":545},"Privacy statement",{"href":546,"dataGaName":547,"dataGaLocation":489},"/privacy/","privacy statement",{"text":549,"config":550},"Cookie preferences",{"dataGaName":551,"dataGaLocation":489,"id":552,"isOneTrustButton":107},"cookie preferences","ot-sdk-btn",{"title":106,"links":554,"subMenu":562},[555,559],{"text":556,"config":557},"DevSecOps platform",{"href":71,"dataGaName":558,"dataGaLocation":489},"devsecops platform",{"text":128,"config":560},{"href":78,"dataGaName":561,"dataGaLocation":489},"ai-assisted development",[563],{"title":564,"links":565},"Topics",[566,571,576,581,586,589,594,599],{"text":567,"config":568},"CICD",{"href":569,"dataGaName":570,"dataGaLocation":489},"/topics/ci-cd/","cicd",{"text":572,"config":573},"GitOps",{"href":574,"dataGaName":575,"dataGaLocation":489},"/topics/gitops/","gitops",{"text":577,"config":578},"DevOps",{"href":579,"dataGaName":580,"dataGaLocation":489},"/topics/devops/","devops",{"text":582,"config":583},"Version Control",{"href":584,"dataGaName":585,"dataGaLocation":489},"/topics/version-control/","version control",{"text":25,"config":587},{"href":588,"dataGaName":21,"dataGaLocation":489},"/topics/devsecops/",{"text":590,"config":591},"Cloud Native",{"href":592,"dataGaName":593,"dataGaLocation":489},"/topics/cloud-native/","cloud native",{"text":595,"config":596},"AI for Coding",{"href":597,"dataGaName":598,"dataGaLocation":489},"/topics/devops/ai-for-coding/","ai for coding",{"text":600,"config":601},"Agentic AI",{"href":602,"dataGaName":603,"dataGaLocation":489},"/topics/agentic-ai/","agentic ai",{"title":605,"links":606},"Solutions",[607,609,611,616,620,623,627,630,632,635,638,643],{"text":149,"config":608},{"href":144,"dataGaName":149,"dataGaLocation":489},{"text":138,"config":610},{"href":121,"dataGaName":122,"dataGaLocation":489},{"text":612,"config":613},"Agile development",{"href":614,"dataGaName":615,"dataGaLocation":489},"/solutions/agile-delivery/","agile delivery",{"text":617,"config":618},"SCM",{"href":134,"dataGaName":619,"dataGaLocation":489},"source code management",{"text":567,"config":621},{"href":126,"dataGaName":622,"dataGaLocation":489},"continuous integration & delivery",{"text":624,"config":625},"Value stream management",{"href":177,"dataGaName":626,"dataGaLocation":489},"value stream management",{"text":572,"config":628},{"href":629,"dataGaName":575,"dataGaLocation":489},"/solutions/gitops/",{"text":187,"config":631},{"href":189,"dataGaName":190,"dataGaLocation":489},{"text":633,"config":634},"Small business",{"href":194,"dataGaName":195,"dataGaLocation":489},{"text":636,"config":637},"Public sector",{"href":199,"dataGaName":200,"dataGaLocation":489},{"text":639,"config":640},"Education",{"href":641,"dataGaName":642,"dataGaLocation":489},"/solutions/education/","education",{"text":644,"config":645},"Financial services",{"href":646,"dataGaName":647,"dataGaLocation":489},"/solutions/finance/","financial services",{"title":207,"links":649},[650,652,654,656,659,661,663,665,667,669,671,673,675],{"text":219,"config":651},{"href":221,"dataGaName":222,"dataGaLocation":489},{"text":224,"config":653},{"href":226,"dataGaName":227,"dataGaLocation":489},{"text":229,"config":655},{"href":231,"dataGaName":232,"dataGaLocation":489},{"text":234,"config":657},{"href":236,"dataGaName":658,"dataGaLocation":489},"docs",{"text":257,"config":660},{"href":259,"dataGaName":5,"dataGaLocation":489},{"text":252,"config":662},{"href":254,"dataGaName":255,"dataGaLocation":489},{"text":261,"config":664},{"href":263,"dataGaName":264,"dataGaLocation":489},{"text":274,"config":666},{"href":276,"dataGaName":277,"dataGaLocation":489},{"text":266,"config":668},{"href":268,"dataGaName":269,"dataGaLocation":489},{"text":279,"config":670},{"href":281,"dataGaName":282,"dataGaLocation":489},{"text":284,"config":672},{"href":286,"dataGaName":287,"dataGaLocation":489},{"text":289,"config":674},{"href":291,"dataGaName":292,"dataGaLocation":489},{"text":294,"config":676},{"href":296,"dataGaName":297,"dataGaLocation":489},{"title":312,"links":678},[679,681,683,685,687,689,691,695,700,702,704,706],{"text":319,"config":680},{"href":321,"dataGaName":314,"dataGaLocation":489},{"text":324,"config":682},{"href":326,"dataGaName":327,"dataGaLocation":489},{"text":332,"config":684},{"href":334,"dataGaName":335,"dataGaLocation":489},{"text":337,"config":686},{"href":339,"dataGaName":340,"dataGaLocation":489},{"text":342,"config":688},{"href":344,"dataGaName":345,"dataGaLocation":489},{"text":347,"config":690},{"href":349,"dataGaName":350,"dataGaLocation":489},{"text":692,"config":693},"Sustainability",{"href":694,"dataGaName":692,"dataGaLocation":489},"/sustainability/",{"text":696,"config":697},"Diversity, inclusion and belonging (DIB)",{"href":698,"dataGaName":699,"dataGaLocation":489},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":352,"config":701},{"href":354,"dataGaName":355,"dataGaLocation":489},{"text":362,"config":703},{"href":364,"dataGaName":365,"dataGaLocation":489},{"text":367,"config":705},{"href":369,"dataGaName":370,"dataGaLocation":489},{"text":707,"config":708},"Modern Slavery Transparency Statement",{"href":709,"dataGaName":710,"dataGaLocation":489},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":712},[713,715,717],{"text":539,"config":714},{"href":541,"dataGaName":542,"dataGaLocation":489},{"text":544,"config":716},{"href":546,"dataGaName":547,"dataGaLocation":489},{"text":549,"config":718},{"dataGaName":551,"dataGaLocation":489,"id":552,"isOneTrustButton":107},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[724],{"_path":725,"_dir":726,"_draft":6,"_partial":6,"_locale":7,"content":727,"config":731,"_id":733,"_type":30,"title":18,"_source":32,"_file":734,"_stem":735,"_extension":35},"/en-us/blog/authors/cesar-saavedra","authors",{"name":18,"config":728},{"headshot":729,"ctfId":730},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659600/Blog/Author%20Headshots/csaavedra1-headshot.jpg","csaavedra1",{"template":732},"BlogAuthor","content:en-us:blog:authors:cesar-saavedra.yml","en-us/blog/authors/cesar-saavedra.yml","en-us/blog/authors/cesar-saavedra",{"_path":737,"_dir":38,"_draft":6,"_partial":6,"_locale":7,"header":738,"eyebrow":739,"blurb":740,"button":741,"secondaryButton":745,"_id":747,"_type":30,"title":748,"_source":32,"_file":749,"_stem":750,"_extension":35},"/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":46,"config":742},{"href":743,"dataGaName":49,"dataGaLocation":744},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":51,"config":746},{"href":53,"dataGaName":54,"dataGaLocation":744},"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":752,"content":753,"config":756,"_id":29,"_type":30,"title":31,"_source":32,"_file":33,"_stem":34,"_extension":35},{"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":754,"heroImage":11,"date":19,"body":20,"category":21,"tags":755},[18],[23,24,25],{"slug":27,"featured":6,"template":28},1761814421859]