[{"data":1,"prerenderedAt":707},["ShallowReactive",2],{"/en-us/blog/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/":3,"navigation-en-us":38,"banner-en-us":453,"footer-en-us":468,"Andrew Fontaine":679,"next-steps-en-us":692},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":28,"_id":31,"_type":32,"title":33,"_source":34,"_file":35,"_stem":36,"_extension":37},"/en-us/blog/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Review Apps for Android with GitLab, fastlane & Appetize.io","See how GitLab and Appetize.io can bring Review Apps to your Android project","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749664102/Blog/Hero%20Images/gitlab-values-cover.png","https://about.gitlab.com/blog/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"How to create Review Apps for Android with GitLab, fastlane, and Appetize.io\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Andrew Fontaine\"}],\n        \"datePublished\": \"2020-05-06\",\n      }",{"title":17,"description":10,"authors":18,"heroImage":11,"date":20,"body":21,"category":22,"tags":23},"How to create Review Apps for Android with GitLab, fastlane, and Appetize.io",[19],"Andrew Fontaine","2020-05-06","\n\n{::options parse_block_html=\"true\" /}\n\n\n\nIn a [previous look at GitLab and _fastlane_], we discussed how _fastlane_ now\nautomatically publishes the Gitter Android app to the Google Play Store, but at\nGitLab, we live on [review apps], and review apps for Android applications didn't\nreally exist... until [Appetize.io] came to our attention.\n\nJust a simple extension of our existing `.gitlab-ci.yml`, we can utilize\nAppetize.io to spin up review apps of our Android application.\n\nIf you'd rather just skip to the end, you can see\n[my MR to the Gitter Android project].\n\n## Setting up Fastlane\n\nFortunately for us, _fastlane_ has integrated support for Appetize.io, so all\nthat's needed to hit Appetize is the addition of a new `lane`:\n\n```diff\ndiff --git a/fastlane/Fastfile b/fastlane/Fastfile\nindex eb47819..f013a86 100644\n--- a/fastlane/Fastfile\n+++ b/fastlane/Fastfile\n@@ -32,6 +32,13 @@ platform :android do\n     gradle(task: \"test\")\n   end\n\n+  desc 'Pushes the app to Appetize and updates a review app'\n+  lane :review do\n+    appetize(api_token: ENV['APPETIZE_TOKEN'],\n+             path: 'app/build/outputs/apk/debug/app-debug.apk',\n+             platform: 'android')\n+  end\n+\n   desc \"Submit a new Internal Build to Play Store\"\n   lane :internal do\n     upload_to_play_store(track: 'internal', apk: 'app/build/outputs/apk/release/app-release.apk')\n```\n\n`APPETIZE_TOKEN` is an Appetize.io API token that can be generated on the\n[Appetize API docs] after signing up for an account. Once we add a new job and\nstage to our `.gitlab-ci.yml`, we will be able to deploy our APK to Appetize and\nrun them in the browser!\n\n```diff\ndiff --git a/.gitlab-ci.yml b/.gitlab-ci.yml\nindex d9863d7..e4d0ce3 100644\n--- a/.gitlab-ci.yml\n+++ b/.gitlab-ci.yml\n@@ -5,6 +5,7 @@ stages:\n   - environment\n   - build\n   - test\n+  - review\n   - internal\n   - alpha\n   - beta\n@@ -81,6 +82,16 @@ buildRelease:\n   environment:\n     name: production\n\n+deployReview:\n+  stage: review\n+  image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\n+  script:\n+    - bundle exec fastlane review\n+  only:\n+    - branches\n+  except:\n+    - master\n+\n testDebug:\n   image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\n   stage: test\n```\n\nGreat! Review apps will be deployed when branches other than `master` build.\nUnfortunately, there is no `environment` block, so there's nothing linking these\ndeployed review apps to GitLab. Let's fix that next.\n\n## Dynamic Environment URLs\n\nPreviously, GitLab only liked environment URLs that used pre-existing CI\nvariables (like `$CI_COMMT_REF_NAME`) in their definition. Since 12.9, however,\na [new way of defining environment urls with alternative variables exists].\n\nBy creating a `dotenv` file and submitting it as an `artifact` in our build, we\ncan define custom variables to use in our environment's URL. As all Appetize.io\napp URLs take the pattern of `https://appetize.io.app/$PUBLIC_KEY`, where\n`$PUBLIC_KEY` is randomly generated when the app is created, we need to get the\npublic key from the Appetize response in our `Fastfile`, and put it in a\n`dotenv` file.\n\n```diff\ndiff --git a/fastlane/Fastfile b/fastlane/Fastfile\nindex 7b5f9d1..ae3867c 100644\n--- a/fastlane/Fastfile\n+++ b/fastlane/Fastfile\n@@ -13,6 +13,13 @@\n # Uncomment the line if you want fastlane to automatically update itself\n # update_fastlane\n\n+\n+def update_deployment_url(pub_key)\n+  File.open('../deploy.env', 'w') do |f|\n+    f.write(\"APPETIZE_PUBLIC_KEY=#{pub_key}\")\n+  end\n+end\n+\n default_platform(:android)\n\n platform :android do\n@@ -37,6 +44,7 @@ platform :android do\n     appetize(api_token: ENV['APPETIZE_TOKEN'],\n              path: 'app/build/outputs/apk/debug/app-debug.apk',\n              platform: 'android')\n+    update_deployment_url(lane_context[SharedValues::APPETIZE_PUBLIC_KEY])\n   end\n\n   desc \"Submit a new Internal Build to Play Store\"\n```\n\nWe also need to add an `environment` block to our `.gitlab-ci.yml` to capture an\nenvironment name and URL.\n\n```diff\ndiff --git a/.gitlab-ci.yml b/.gitlab-ci.yml\nindex f5a8648..c834077 100644\n--- a/.gitlab-ci.yml\n+++ b/.gitlab-ci.yml\n@@ -85,12 +85,18 @@ buildCreateReleaseNotes:\n deployReview:\n   stage: review\n   image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\n+  environment:\n+    name: review/$CI_COMMIT_REF_NAME\n+    url: https://appetize.io/app/$APPETIZE_PUBLIC_KEY\n   script:\n     - bundle exec fastlane review\n   only:\n     - branches\n   except:\n     - master\n+  artifacts:\n+    reports:\n+      dotenv: deploy.env\n\n testDebug:\n   image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\n```\n\nOnce committed, pushed, and a pipeline runs, we should see our environment\ndeployed!\n\n![Our first review environment][first-review-app]\n\n## Optimizing Updates\n\nAfter running with this for a bit, we realized that we were accidentally\ncreating a new app on Appetize.io with every new build! Their docs\n[specify how to update existing apps], so we went about seeing if we could\nsmartly update existing environments.\n\nSpoiler alert: We could.\n\nFirst, we need to save the public key granted to us by Appetize.io somewhere. We\ndecided to put it in a JSON file and save that as an artifact of the build.\nFortunately, the `Fastfile` is just ruby, which allows us to quickly write it\nout to a file with a few lines of code, as well as attempt to fetch the artifact\nfor the last build of the current branch.\n\n```diff\ndiff --git a/fastlane/Fastfile b/fastlane/Fastfile\nindex ae3867c..61e9226 100644\n--- a/fastlane/Fastfile\n+++ b/fastlane/Fastfile\n@@ -13,8 +13,32 @@\n # Uncomment the line if you want fastlane to automatically update itself\n # update_fastlane\n\n+require 'net/http'\n+require 'json'\n+\n+GITLAB_TOKEN = ENV['PRIVATE_TOKEN']\n+PROJECT_ID = ENV['CI_PROJECT_ID']\n+REF = ENV['CI_COMMIT_REF_NAME']\n+JOB = ENV['CI_JOB_NAME']\n+API_ROOT = ENV['CI_API_V4_URL']\n+\n+def public_key\n+  uri = URI(\"#{API_ROOT}/projects/#{PROJECT_ID}/jobs/artifacts/#{REF}/raw/appetize-information.json?job=#{JOB}\")\n+  http = Net::HTTP.new(uri.host, uri.port)\n+  http.use_ssl = true\n+  req = Net::HTTP::Get.new(uri)\n+  req['PRIVATE-TOKEN'] = GITLAB_TOKEN\n+  response = http.request(req)\n+  return '' if response.code.equal?('404')\n+\n+  appetize_info = JSON.parse(response.body)\n+  appetize_info['publicKey']\n+end\n\n def update_deployment_url(pub_key)\n+  File.open('../appetize-information.json', 'w') do |f|\n+    f.write(JSON.generate(publicKey: pub_key))\n+  end\n   File.open('../deploy.env', 'w') do |f|\n     f.write(\"APPETIZE_PUBLIC_KEY=#{pub_key}\")\n   end\n@@ -42,6 +66,7 @@ platform :android do\n   desc 'Pushes the app to Appetize and updates a review app'\n   lane :review do\n     appetize(api_token: ENV['APPETIZE_TOKEN'],\n+             public_key: public_key,\n              path: 'app/build/outputs/apk/debug/app-debug.apk',\n              platform: 'android')\n     update_deployment_url(lane_context[SharedValues::APPETIZE_PUBLIC_KEY])\n```\n\nWhen we go to deploy our app to Appetize, we hit the [Jobs API] to see if we\nhave a public key for this branch. If the API returns a `404`, we know we are\nbuilding a fresh branch and return an empty string, else we parse the JSON and\nreturn our public key. The [Fastlane docs] state the `appetize` action can take\na `public_key` to update an existing app. Here, `''` is considered the same as\n_not_ providing a public key, so a new application is still deployed as we expect.\n\n**NOTE:** If you've read the `diff` closely, you'll notice the usage of an\nenvironment variable called `PRIVATE_TOKEN`. This is a GitLab private token\ncreated with the `read_api` scope and injected into our build as an environment\nvariable. This is required to authenticate with the GitLab API and fetch\nartifacts.\n\nOnce we update `.gitlab-ci.yml` to save the new `appetize-information.json` file\nas an artifact, later builds on the same branch will be smart and update the\nexisting Appetize app!\n\n```diff\ndiff --git a/.gitlab-ci.yml b/.gitlab-ci.yml\nindex c834077..54cf3f6 100644\n--- a/.gitlab-ci.yml\n+++ b/.gitlab-ci.yml\n@@ -95,6 +95,8 @@ deployReview:\n   except:\n     - master\n   artifacts:\n+    paths:\n+      - appetize-information.json\n     reports:\n       dotenv: deploy.env\n```\n\n## Cleaning up\n\nAll that's left is to delete old apps from Appetize once we don't need them\nanymore. We can do that by leveraging `on_stop` and creating a `stop` job that\nwill delete our app from Appetize.io\n\n```diff\ndiff --git a/.gitlab-ci.yml b/.gitlab-ci.yml\nindex 54cf3f6..f6ecf7e 100644\n--- a/.gitlab-ci.yml\n+++ b/.gitlab-ci.yml\n@@ -10,6 +10,7 @@ stages:\n   - alpha\n   - beta\n   - production\n+  - stop\n\n\n .updateContainerJob:\n@@ -88,6 +89,7 @@ deployReview:\n   environment:\n     name: review/$CI_COMMIT_REF_NAME\n     url: https://appetize.io/app/$APPETIZE_PUBLIC_KEY\n+    on_stop: stopReview\n   script:\n     - bundle exec fastlane review\n   only:\n@@ -100,6 +102,22 @@ deployReview:\n     reports:\n       dotenv: deploy.env\n\n+stopReview:\n+  stage: stop\n+  environment:\n+    name: review/$CI_COMMIT_REF_NAME\n+    action: stop\n+  variables:\n+    GIT_STRATEGY: none\n+  when: manual\n+  only:\n+    - branches\n+  except:\n+    - master\n+  script:\n+    - apt-get -y update && apt-get -y upgrade && apt-get -y install jq curl\n+    - curl --request DELETE https://$APPETIZE_TOKEN@api.appetize.io/v1/apps/`jq -r '.publicKey' \u003C appetize-information.json`\n+\n testDebug:\n   image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG\n   stage: test\n```\n\nOnce your MR is merged and your branch is deleted, the `stopReview` job runs,\ncalling the [`DELETE` endpoint of the Appetize.io API] with the public key that\nis contained in `appetize-information.json`. We don't need to fetch\n`appetize-information.json` because the artifact is already present in our build\ncontext. This is because the `stop` stage happens _after_ the `review` stage.\n\n![A merge request with a deployed review app][merge-request-with-review-app]\n\n## Conclusion\n\nThanks to some integration with _fastlane_ and the addition of a couple\nenvironment variables, having the ability to create review apps for an Android\nproject was surpsingly simple. GitLab's review apps are not _just_ for web-based\nprojects, even though it may take a little tinkering to get working. Appetize.io\nalso supports iOS applications, so all mobile native applications can be turned\ninto review apps. I would love to see this strategy be applied to a React Native\nproject as well!\n\n[previous look at gitlab and _fastlane_]: /blog/android-publishing-with-gitlab-and-fastlane/\n[my mr to the gitter android project]: https://gitlab.com/gitlab-org/gitter/gitter-android-app/-/merge_requests/167\n[review apps]: https://docs.gitlab.com/ee/ci/review_apps/#review-apps\n[appetize.io]: https://appetize.io\n[appetize api docs]: https://appetize.io/docs#request-api-token\n[new way of defining environment urls with alternative variables exists]: https://docs.gitlab.com/ee/ci/environments/index.html#set-dynamic-environment-urls-after-a-job-finishes\n[first-review-app]: /images/blogimages/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/first-review-app.png\n[specify how to update existing apps]: https://appetize.io/docs#updating-apps\n[jobs api]: https://docs.gitlab.com/ee/api/jobs.html#download-a-single-artifact-file-from-specific-tag-or-branch\n[fastlane docs]: https://docs.fastlane.tools/actions/appetize/\n[`delete` endpoint of the appetize.io api]: https://appetize.io/docs#deleting-apps\n[merge-request-with-review-app]: /images/blogimages/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io/merge-request-with-review-app.png\n","unfiltered",[24,25,26,27],"CI/CD","integrations","features","tutorial",{"slug":29,"featured":6,"template":30},"how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io","BlogPost","content:en-us:blog:how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io.yml","yaml","How To Create Review Apps For Android With Gitlab Fastlane And Appetize Dot Io","content","en-us/blog/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io.yml","en-us/blog/how-to-create-review-apps-for-android-with-gitlab-fastlane-and-appetize-dot-io","yml",{"_path":39,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":41,"_id":449,"_type":32,"title":450,"_source":34,"_file":451,"_stem":452,"_extension":37},"/shared/en-us/main-navigation","en-us",{"logo":42,"freeTrial":47,"sales":52,"login":57,"items":62,"search":390,"minimal":421,"duo":440},{"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,202,207,311,371],{"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":109,"config":110,"link":112,"lists":116,"footer":184},"Product",true,{"dataNavLevelOne":111},"solutions",{"text":113,"config":114},"View all Solutions",{"href":115,"dataGaName":111,"dataGaLocation":46},"/solutions/",[117,141,163],{"title":118,"description":119,"link":120,"items":125},"Automation","CI/CD and automation to accelerate deployment",{"config":121},{"icon":122,"href":123,"dataGaName":124,"dataGaLocation":46},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[126,129,133,137],{"text":24,"config":127},{"href":128,"dataGaLocation":46,"dataGaName":24},"/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":123,"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/security-compliance/","security and compliance","ShieldCheckLight",[150,153,158],{"text":151,"config":152},"Security & Compliance",{"href":146,"dataGaLocation":46,"dataGaName":151},{"text":154,"config":155},"Software Supply Chain Security",{"href":156,"dataGaLocation":46,"dataGaName":157},"/solutions/supply-chain/","Software supply chain security",{"text":159,"config":160},"Compliance & Governance",{"href":161,"dataGaLocation":46,"dataGaName":162},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":164,"link":165,"items":170},"Measurement",{"config":166},{"icon":167,"href":168,"dataGaName":169,"dataGaLocation":46},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[171,175,179],{"text":172,"config":173},"Visibility & Measurement",{"href":168,"dataGaLocation":46,"dataGaName":174},"Visibility and Measurement",{"text":176,"config":177},"Value Stream Management",{"href":178,"dataGaLocation":46,"dataGaName":176},"/solutions/value-stream-management/",{"text":180,"config":181},"Analytics & Insights",{"href":182,"dataGaLocation":46,"dataGaName":183},"/solutions/analytics-and-insights/","Analytics and insights",{"title":185,"items":186},"GitLab for",[187,192,197],{"text":188,"config":189},"Enterprise",{"href":190,"dataGaLocation":46,"dataGaName":191},"/enterprise/","enterprise",{"text":193,"config":194},"Small Business",{"href":195,"dataGaLocation":46,"dataGaName":196},"/small-business/","small business",{"text":198,"config":199},"Public Sector",{"href":200,"dataGaLocation":46,"dataGaName":201},"/solutions/public-sector/","public sector",{"text":203,"config":204},"Pricing",{"href":205,"dataGaName":206,"dataGaLocation":46,"dataNavLevelOne":206},"/pricing/","pricing",{"text":208,"config":209,"link":211,"lists":215,"feature":298},"Resources",{"dataNavLevelOne":210},"resources",{"text":212,"config":213},"View all resources",{"href":214,"dataGaName":210,"dataGaLocation":46},"/resources/",[216,248,270],{"title":217,"items":218},"Getting started",[219,224,229,234,239,244],{"text":220,"config":221},"Install",{"href":222,"dataGaName":223,"dataGaLocation":46},"/install/","install",{"text":225,"config":226},"Quick start guides",{"href":227,"dataGaName":228,"dataGaLocation":46},"/get-started/","quick setup checklists",{"text":230,"config":231},"Learn",{"href":232,"dataGaLocation":46,"dataGaName":233},"https://university.gitlab.com/","learn",{"text":235,"config":236},"Product documentation",{"href":237,"dataGaName":238,"dataGaLocation":46},"https://docs.gitlab.com/","product documentation",{"text":240,"config":241},"Best practice videos",{"href":242,"dataGaName":243,"dataGaLocation":46},"/getting-started-videos/","best practice videos",{"text":245,"config":246},"Integrations",{"href":247,"dataGaName":25,"dataGaLocation":46},"/integrations/",{"title":249,"items":250},"Discover",[251,256,260,265],{"text":252,"config":253},"Customer success stories",{"href":254,"dataGaName":255,"dataGaLocation":46},"/customers/","customer success stories",{"text":257,"config":258},"Blog",{"href":259,"dataGaName":5,"dataGaLocation":46},"/blog/",{"text":261,"config":262},"Remote",{"href":263,"dataGaName":264,"dataGaLocation":46},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":266,"config":267},"TeamOps",{"href":268,"dataGaName":269,"dataGaLocation":46},"/teamops/","teamops",{"title":271,"items":272},"Connect",[273,278,283,288,293],{"text":274,"config":275},"GitLab Services",{"href":276,"dataGaName":277,"dataGaLocation":46},"/services/","services",{"text":279,"config":280},"Community",{"href":281,"dataGaName":282,"dataGaLocation":46},"/community/","community",{"text":284,"config":285},"Forum",{"href":286,"dataGaName":287,"dataGaLocation":46},"https://forum.gitlab.com/","forum",{"text":289,"config":290},"Events",{"href":291,"dataGaName":292,"dataGaLocation":46},"/events/","events",{"text":294,"config":295},"Partners",{"href":296,"dataGaName":297,"dataGaLocation":46},"/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},"/images/navigation/the-source-promo-card.svg",{"text":307,"config":308},"Read the latest",{"href":309,"dataGaName":310,"dataGaLocation":46},"/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":46},"/company/","about",{"text":324,"config":325,"footerGa":328},"Jobs",{"href":326,"dataGaName":327,"dataGaLocation":46},"/jobs/","jobs",{"dataGaName":327},{"text":289,"config":330},{"href":291,"dataGaName":292,"dataGaLocation":46},{"text":332,"config":333},"Leadership",{"href":334,"dataGaName":335,"dataGaLocation":46},"/company/team/e-group/","leadership",{"text":337,"config":338},"Team",{"href":339,"dataGaName":340,"dataGaLocation":46},"/company/team/","team",{"text":342,"config":343},"Handbook",{"href":344,"dataGaName":345,"dataGaLocation":46},"https://handbook.gitlab.com/","handbook",{"text":347,"config":348},"Investor relations",{"href":349,"dataGaName":350,"dataGaLocation":46},"https://ir.gitlab.com/","investor relations",{"text":352,"config":353},"Trust Center",{"href":354,"dataGaName":355,"dataGaLocation":46},"/security/","trust center",{"text":357,"config":358},"AI Transparency Center",{"href":359,"dataGaName":360,"dataGaLocation":46},"/ai-transparency-center/","ai transparency center",{"text":362,"config":363},"Newsletter",{"href":364,"dataGaName":365,"dataGaLocation":46},"/company/contact/","newsletter",{"text":367,"config":368},"Press",{"href":369,"dataGaName":370,"dataGaLocation":46},"/press/","press",{"text":372,"config":373,"lists":374},"Contact us",{"dataNavLevelOne":314},[375],{"items":376},[377,380,385],{"text":53,"config":378},{"href":55,"dataGaName":379,"dataGaLocation":46},"talk to sales",{"text":381,"config":382},"Get help",{"href":383,"dataGaName":384,"dataGaLocation":46},"/support/","get help",{"text":386,"config":387},"Customer portal",{"href":388,"dataGaName":389,"dataGaLocation":46},"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":60,"dataGaName":397,"dataGaLocation":398},"search login","search",{"text":400,"default":401},"Suggestions",[402,404,408,410,414,418],{"text":75,"config":403},{"href":80,"dataGaName":75,"dataGaLocation":398},{"text":405,"config":406},"Code Suggestions (AI)",{"href":407,"dataGaName":405,"dataGaLocation":398},"/solutions/code-suggestions/",{"text":24,"config":409},{"href":128,"dataGaName":24,"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":88,"dataGaName":419,"dataGaLocation":398},{"freeTrial":422,"mobileIcon":427,"desktopIcon":432,"secondaryButton":435},{"text":423,"config":424},"Start free trial",{"href":425,"dataGaName":51,"dataGaLocation":426},"https://gitlab.com/-/trials/new/","nav",{"altText":428,"config":429},"Gitlab Icon",{"src":430,"dataGaName":431,"dataGaLocation":426},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":428,"config":433},{"src":434,"dataGaName":431,"dataGaLocation":426},"/images/brand/gitlab-logo-type.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":80,"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},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":454,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"title":455,"button":456,"image":460,"config":463,"_id":465,"_type":32,"_source":34,"_file":466,"_stem":467,"_extension":37},"/shared/en-us/banner","is now in public beta!",{"text":86,"config":457},{"href":458,"dataGaName":459,"dataGaLocation":46},"/gitlab-duo/agent-platform/","duo banner",{"config":461},{"src":462},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":464},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":469,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"data":470,"_id":675,"_type":32,"title":676,"_source":34,"_file":677,"_stem":678,"_extension":37},"/shared/en-us/main-footer",{"text":471,"source":472,"edit":478,"contribute":483,"config":488,"items":493,"minimal":667},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":473,"config":474},"View page source",{"href":475,"dataGaName":476,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":479,"config":480},"Edit this page",{"href":481,"dataGaName":482,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":484,"config":485},"Please contribute",{"href":486,"dataGaName":487,"dataGaLocation":477},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":489,"facebook":490,"youtube":491,"linkedin":492},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[494,517,574,603,637],{"title":64,"links":495,"subMenu":500},[496],{"text":497,"config":498},"DevSecOps platform",{"href":73,"dataGaName":499,"dataGaLocation":477},"devsecops platform",[501],{"title":203,"links":502},[503,507,512],{"text":504,"config":505},"View plans",{"href":205,"dataGaName":506,"dataGaLocation":477},"view plans",{"text":508,"config":509},"Why Premium?",{"href":510,"dataGaName":511,"dataGaLocation":477},"/pricing/premium/","why premium",{"text":513,"config":514},"Why Ultimate?",{"href":515,"dataGaName":516,"dataGaLocation":477},"/pricing/ultimate/","why ultimate",{"title":518,"links":519},"Solutions",[520,525,528,530,535,540,544,547,551,556,558,561,564,569],{"text":521,"config":522},"Digital transformation",{"href":523,"dataGaName":524,"dataGaLocation":477},"/topics/digital-transformation/","digital transformation",{"text":151,"config":526},{"href":146,"dataGaName":527,"dataGaLocation":477},"security & compliance",{"text":140,"config":529},{"href":123,"dataGaName":124,"dataGaLocation":477},{"text":531,"config":532},"Agile development",{"href":533,"dataGaName":534,"dataGaLocation":477},"/solutions/agile-delivery/","agile delivery",{"text":536,"config":537},"Cloud transformation",{"href":538,"dataGaName":539,"dataGaLocation":477},"/topics/cloud-native/","cloud transformation",{"text":541,"config":542},"SCM",{"href":136,"dataGaName":543,"dataGaLocation":477},"source code management",{"text":24,"config":545},{"href":128,"dataGaName":546,"dataGaLocation":477},"continuous integration & delivery",{"text":548,"config":549},"Value stream management",{"href":178,"dataGaName":550,"dataGaLocation":477},"value stream management",{"text":552,"config":553},"GitOps",{"href":554,"dataGaName":555,"dataGaLocation":477},"/solutions/gitops/","gitops",{"text":188,"config":557},{"href":190,"dataGaName":191,"dataGaLocation":477},{"text":559,"config":560},"Small business",{"href":195,"dataGaName":196,"dataGaLocation":477},{"text":562,"config":563},"Public sector",{"href":200,"dataGaName":201,"dataGaLocation":477},{"text":565,"config":566},"Education",{"href":567,"dataGaName":568,"dataGaLocation":477},"/solutions/education/","education",{"text":570,"config":571},"Financial services",{"href":572,"dataGaName":573,"dataGaLocation":477},"/solutions/finance/","financial services",{"title":208,"links":575},[576,578,580,582,585,587,589,591,593,595,597,599,601],{"text":220,"config":577},{"href":222,"dataGaName":223,"dataGaLocation":477},{"text":225,"config":579},{"href":227,"dataGaName":228,"dataGaLocation":477},{"text":230,"config":581},{"href":232,"dataGaName":233,"dataGaLocation":477},{"text":235,"config":583},{"href":237,"dataGaName":584,"dataGaLocation":477},"docs",{"text":257,"config":586},{"href":259,"dataGaName":5,"dataGaLocation":477},{"text":252,"config":588},{"href":254,"dataGaName":255,"dataGaLocation":477},{"text":261,"config":590},{"href":263,"dataGaName":264,"dataGaLocation":477},{"text":274,"config":592},{"href":276,"dataGaName":277,"dataGaLocation":477},{"text":266,"config":594},{"href":268,"dataGaName":269,"dataGaLocation":477},{"text":279,"config":596},{"href":281,"dataGaName":282,"dataGaLocation":477},{"text":284,"config":598},{"href":286,"dataGaName":287,"dataGaLocation":477},{"text":289,"config":600},{"href":291,"dataGaName":292,"dataGaLocation":477},{"text":294,"config":602},{"href":296,"dataGaName":297,"dataGaLocation":477},{"title":312,"links":604},[605,607,609,611,613,615,617,621,626,628,630,632],{"text":319,"config":606},{"href":321,"dataGaName":314,"dataGaLocation":477},{"text":324,"config":608},{"href":326,"dataGaName":327,"dataGaLocation":477},{"text":332,"config":610},{"href":334,"dataGaName":335,"dataGaLocation":477},{"text":337,"config":612},{"href":339,"dataGaName":340,"dataGaLocation":477},{"text":342,"config":614},{"href":344,"dataGaName":345,"dataGaLocation":477},{"text":347,"config":616},{"href":349,"dataGaName":350,"dataGaLocation":477},{"text":618,"config":619},"Sustainability",{"href":620,"dataGaName":618,"dataGaLocation":477},"/sustainability/",{"text":622,"config":623},"Diversity, inclusion and belonging (DIB)",{"href":624,"dataGaName":625,"dataGaLocation":477},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":352,"config":627},{"href":354,"dataGaName":355,"dataGaLocation":477},{"text":362,"config":629},{"href":364,"dataGaName":365,"dataGaLocation":477},{"text":367,"config":631},{"href":369,"dataGaName":370,"dataGaLocation":477},{"text":633,"config":634},"Modern Slavery Transparency Statement",{"href":635,"dataGaName":636,"dataGaLocation":477},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":638,"links":639},"Contact Us",[640,643,645,647,652,657,662],{"text":641,"config":642},"Contact an expert",{"href":55,"dataGaName":56,"dataGaLocation":477},{"text":381,"config":644},{"href":383,"dataGaName":384,"dataGaLocation":477},{"text":386,"config":646},{"href":388,"dataGaName":389,"dataGaLocation":477},{"text":648,"config":649},"Status",{"href":650,"dataGaName":651,"dataGaLocation":477},"https://status.gitlab.com/","status",{"text":653,"config":654},"Terms of use",{"href":655,"dataGaName":656,"dataGaLocation":477},"/terms/","terms of use",{"text":658,"config":659},"Privacy statement",{"href":660,"dataGaName":661,"dataGaLocation":477},"/privacy/","privacy statement",{"text":663,"config":664},"Cookie preferences",{"dataGaName":665,"dataGaLocation":477,"id":666,"isOneTrustButton":109},"cookie preferences","ot-sdk-btn",{"items":668},[669,671,673],{"text":653,"config":670},{"href":655,"dataGaName":656,"dataGaLocation":477},{"text":658,"config":672},{"href":660,"dataGaName":661,"dataGaLocation":477},{"text":663,"config":674},{"dataGaName":665,"dataGaLocation":477,"id":666,"isOneTrustButton":109},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[680],{"_path":681,"_dir":682,"_draft":6,"_partial":6,"_locale":7,"content":683,"config":687,"_id":689,"_type":32,"title":19,"_source":34,"_file":690,"_stem":691,"_extension":37},"/en-us/blog/authors/andrew-fontaine","authors",{"name":19,"config":684},{"headshot":685,"ctfId":686},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672447/Blog/Author%20Headshots/afontaine-headshot.jpg","afontaine",{"template":688},"BlogAuthor","content:en-us:blog:authors:andrew-fontaine.yml","en-us/blog/authors/andrew-fontaine.yml","en-us/blog/authors/andrew-fontaine",{"_path":693,"_dir":40,"_draft":6,"_partial":6,"_locale":7,"header":694,"eyebrow":695,"blurb":696,"button":697,"secondaryButton":701,"_id":703,"_type":32,"title":704,"_source":34,"_file":705,"_stem":706,"_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":698},{"href":699,"dataGaName":51,"dataGaLocation":700},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":53,"config":702},{"href":55,"dataGaName":56,"dataGaLocation":700},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1754424504705]