[{"data":1,"prerenderedAt":1986},["ShallowReactive",2],{"/blog/setting-up-gitlab-ci-for-ios-projects":3,"navigation-en-us":1204,"banner-en-us":1627,"footer-en-us":1637,"blog-post-authors-en-us-Angelo Stavrow":1881,"blog-related-posts-en-us-setting-up-gitlab-ci-for-ios-projects":1896,"blog-promotions-en-us":1922,"next-steps-en-us":1976},{"id":4,"title":5,"authors":6,"body":8,"category":1173,"date":1174,"description":1175,"extension":1176,"externalUrl":1177,"faq":1177,"featured":1178,"heroImage":1179,"meta":1180,"navigation":470,"path":1195,"seo":1196,"slug":1200,"stem":1201,"tags":1177,"template":1202,"updatedDate":1177,"__hash__":1203},"blogPosts/en-us/blog/setting-up-gitlab-ci-for-ios-projects.md","Setting up GitLab CI for iOS projects",[7],"Angelo Stavrow",{"type":9,"value":10,"toc":1161},"minimark",[11,31,34,39,46,54,57,84,93,97,100,107,110,122,125,129,132,139,155,161,164,170,185,191,194,207,213,220,229,235,238,241,248,280,283,301,316,327,381,390,394,407,506,513,521,531,537,552,558,565,571,581,587,597,604,610,613,616,669,676,679,685,691,706,709,745,748,795,805,811,821,829,832,837,841,852,862,865,869,872,927,930,936,942,949,955,961,967,970,974,977,983,986,992,999,1005,1008,1012,1019,1033,1039,1064,1080,1083,1089,1093,1140,1145,1157],[12,13,14],"p",{},[15,16,17,18,24,25,30],"em",{},"Note: This blog post was published in 2016. For more current info, check out ",[19,20,23],"a",{"href":21,"rel":22},"https://about.gitlab.com/blog/ios-cicd-with-gitlab/",[],"Tutorial: iOS CI/CD with GitLab"," from June 2023 and our ",[19,26,29],{"href":27,"rel":28},"https://docs.gitlab.com/ci/mobile_devops/",[],"documentation on GitLab Mobile DevOps",". Those both cover fastlane, fastlane match, code signing, signing certificates, provision profiles, App Store Connect, and more.",[12,32,33],{},"In this post I'll show you how to set up GitLab CI for your iOS mobile projects,\nstep-by-step, from start to finish.",[35,36,38],"h3",{"id":37},"why-ci","Why CI?",[12,40,41,45],{},[19,42,44],{"href":43},"/topics/ci-cd/","Continuous integration"," (CI) is great tool for helping developers be more productive and write higher-\nquality code. By automatically running a suite of tests every time a commit is\npushed, everyone can see the results of changes to the codebase, and take action\nto make integration faster and easier.",[12,47,48,49,53],{},"GitLab ",[19,50,52],{"href":51},"/solutions/continuous-integration/","comes with CI built-in"," for all\nprojects, for free.",[12,55,56],{},"It's beyond the scope of this tutorial to go into details on best practices,\nworkflows, and advantages/disadvantages of CI. In short, however, here's what\nhappens when you enable it for your Xcode project:",[58,59,60,64,67,70,78,81],"ol",{},[61,62,63],"li",{},"You make changes to your copy of the codebase and push a commit to GitLab.",[61,65,66],{},"GitLab recognizes that the codebase has changed.",[61,68,69],{},"GitLab triggers a build with the GitLab Runner you set up on your Mac for the project.",[61,71,72,73,77],{},"The GitLab Runner runs through the build and test process you specified in the ",[74,75,76],"code",{},".gitlab-ci.yml"," configuration file.",[61,79,80],{},"The GitLab Runner reports its results back to GitLab.",[61,82,83],{},"GitLab shows you the results of the build.",[12,85,86,87,92],{},"This post builds on ",[19,88,91],{"href":89,"rel":90},"http://www.thejeremywhite.com/blog/xcode-gitlab-ci-setup.html",[],"Jeremy White's blog post",",\ngoing into a little more detail and correcting some steps for the environment\ndescribed in the next section.",[35,94,96],{"id":95},"assumptions-and-environment","Assumptions and environment",[12,98,99],{},"This post will provide a step-by-step guide to setting up GitLab CI for your iOS\nprojects, from start to finish. First, however, we need to make a few assumptions.",[12,101,102,103,106],{},"GitLab's strategy document hinges on one key idea: ",[15,104,105],{},"everyone can contribute",". As such, this post is written for readers\nof nearly all levels of experience. However, given that CI is a relatively\nadvanced topic, we're going to assume some basic knowledge of how to create\nXcode and GitLab projects, as well as some familiarity with Terminal and git.",[12,108,109],{},"This post was written with the following development environment in mind:",[111,112,113,116,119],"ul",{},[61,114,115],{},"A Mac running macOS 10.11.3 \"El Capitan\"",[61,117,118],{},"Xcode 7.2.1 with command-line tools and the iOS 9.2 SDK installed",[61,120,121],{},"GitLab.com v8.5",[12,123,124],{},"We'll also assume you've already created a new GitLab project. If you haven't,\ngo ahead and do that now.",[35,126,128],{"id":127},"setting-up-your-xcode-project","Setting up your Xcode project",[12,130,131],{},"We'll start by creating a new single-view iOS project in Xcode.",[12,133,134],{},[135,136],"img",{"alt":137,"src":138},"Creating a new Xcode project.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399016/blog/Content%20Images/setting-up-gitlab-for-ios-projects/1_create-new-xcode-project.png",[12,140,141,142,146,147,150,151,154],{},"Give your project a name and make certain that the ",[143,144,145],"strong",{},"Include Unit Tests"," and\n",[143,148,149],{},"Include UI Tests"," options are enabled for the project. Xcode will create a\ntemplate test class with some sample tests, which we'll use in this post as the\ntest suite that GitLab CI runs to verify a build. Choose a name for your project\nand click on ",[143,152,153],{},"Next",".",[12,156,157],{},[135,158],{"alt":159,"src":160},"Enable unit and UI tests in your project","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399016/blog/Content%20Images/setting-up-gitlab-for-ios-projects/2_enable-unit-tests.png",[12,162,163],{},"Choose where you'll save your iOS project. If you like, let Xcode create the git\nrepository on your Mac.",[12,165,166],{},[135,167],{"alt":168,"src":169},"Let Xcode initialize your git repository.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399016/blog/Content%20Images/setting-up-gitlab-for-ios-projects/3_create-git-repository.png",[12,171,172,173,178,179,184],{},"Once Xcode has created and opened your iOS project, you need to ",[19,174,177],{"href":175,"rel":176},"https://developer.apple.com/library/ios/recipes/xcode_help-scheme_editor/Articles/SchemeShare.html",[],"share its scheme",". Apple's ",[19,180,183],{"href":181,"rel":182},"https://developer.apple.com/library/ios/recipes/xcode_help-scheme_editor/Articles/SchemeDialog.html",[],"documentation"," defines schemes nicely:",[186,187,188],"blockquote",{},[12,189,190],{},"A scheme is a collection of settings that specify which targets to build, what build configuration to use, and the executable environment to use when the product specified by the target is launched.",[12,192,193],{},"By sharing your scheme, GitLab CI gets context it needs to build and test your project.",[12,195,196,197,200,201,200,204,154],{},"To share a scheme in Xcode, choose ",[143,198,199],{},"Product"," > ",[143,202,203],{},"Scheme",[143,205,206],{},"Manage Schemes",[12,208,209],{},[135,210],{"alt":211,"src":212},"Share your scheme.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399016/blog/Content%20Images/setting-up-gitlab-for-ios-projects/4_share-xcode-scheme.png",[12,214,215,216,219],{},"Click on the ",[143,217,218],{},"Close"," button.",[12,221,222,223,200,225,228],{},"Your Xcode project has been created with two test files; one includes sample unit\ntests, and the other includes sample UI tests. You can run ",[143,224,199],{},[143,226,227],{},"Test","\nto run these tests, which will build your project, launch the Simulator, install\nthe project on the Simulator device, and run the test suite. You can see the\nresults right in Xcode:",[12,230,231],{},[135,232],{"alt":233,"src":234},"Test suite success in Xcode.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399017/blog/Content%20Images/setting-up-gitlab-for-ios-projects/5_test-suite-success-in-xcode.png",[12,236,237],{},"The green checkmarks next to the test functions (both in the file, and in the\nTest navigator) show that all tests passed. We won't be referring to the Xcode\nproject anymore, so if you like, you can close it.",[12,239,240],{},"Next, open Terminal and navigate to the folder you created for your iOS project.",[12,242,243,244,247],{},"It's convenient to add a standard ",[74,245,246],{},".gitignore"," file. For a Swift project, enter:",[249,250,255],"pre",{"className":251,"code":252,"language":253,"meta":254,"style":254},"language-shell shiki shiki-themes github-light","$ curl -o .gitignore https://www.toptal.com/developers/gitignore/api/swift\n","shell","",[74,256,257],{"__ignoreMap":254},[258,259,262,266,270,274,277],"span",{"class":260,"line":261},"line",1,[258,263,265],{"class":264},"s7eDp","$",[258,267,269],{"class":268},"sYBdl"," curl",[258,271,273],{"class":272},"sYu0t"," -o",[258,275,276],{"class":268}," .gitignore",[258,278,279],{"class":268}," https://www.toptal.com/developers/gitignore/api/swift\n",[12,281,282],{},"For an Objective-C project, enter:",[249,284,286],{"className":251,"code":285,"language":253,"meta":254,"style":254},"$ curl -o .gitignore https://www.gitignore.io/api/objective-c\n",[74,287,288],{"__ignoreMap":254},[258,289,290,292,294,296,298],{"class":260,"line":261},[258,291,265],{"class":264},[258,293,269],{"class":268},[258,295,273],{"class":272},[258,297,276],{"class":268},[258,299,300],{"class":268}," https://www.gitignore.io/api/objective-c\n",[12,302,303,304,307,308,313,314,154],{},"The ",[74,305,306],{},"curl"," command conveniently downloads the contents of the page at the given ",[19,309,312],{"href":310,"rel":311},"https://gitignore.io",[],"gitignore.io"," URL into a file named ",[74,315,246],{},[12,317,318,319,322,323,326],{},"If Xcode initialized the git repository for you, you'll need to set the origin\nurl to your GitLab project (replacing ",[74,320,321],{},"\u003Cusername>"," with your GitLab username\nand ",[74,324,325],{},"\u003Cproject>"," with the project name:",[249,328,330],{"className":251,"code":329,"language":253,"meta":254,"style":254},"$ git remote add origin git@gitlab.com:\u003Cusername>/\u003Cproject>.git\n",[74,331,332],{"__ignoreMap":254},[258,333,334,336,339,342,345,348,351,355,358,362,365,368,370,373,376,378],{"class":260,"line":261},[258,335,265],{"class":264},[258,337,338],{"class":268}," git",[258,340,341],{"class":268}," remote",[258,343,344],{"class":268}," add",[258,346,347],{"class":268}," origin",[258,349,350],{"class":268}," git@gitlab.com:",[258,352,354],{"class":353},"sD7c4","\u003C",[258,356,357],{"class":268},"usernam",[258,359,361],{"class":360},"sgsFI","e",[258,363,364],{"class":353},">",[258,366,367],{"class":268},"/",[258,369,354],{"class":353},[258,371,372],{"class":268},"projec",[258,374,375],{"class":360},"t",[258,377,364],{"class":353},[258,379,380],{"class":268},".git\n",[12,382,383,384,389],{},"The final step here is to ",[19,385,388],{"href":386,"rel":387},"https://github.com/supermarin/xcpretty",[],"install xcpretty",".\nWhen Xcode builds and tests your project, xcpretty will transform the output into\nsomething more readable for you.",[35,391,393],{"id":392},"installing-and-registering-the-gitlab-runner","Installing and registering the GitLab Runner",[12,395,396,397,402,403,406],{},"The GitLab Runner is a service that's installed on your Mac, which runs the build\nand test process that you set up in a configuration file. You can follow the\n",[19,398,401],{"href":399,"rel":400},"https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/install/osx.md",[],"installation instructions for macOS",",\nbut we'll need to make some changes to the ",[15,404,405],{},"register the runner"," step:",[249,408,412],{"className":409,"code":410,"language":411,"meta":254,"style":254},"language-yaml shiki shiki-themes github-light","$ gitlab-ci-multi-runner register\nWARNING: Running in user-mode.                     WARNING: The user-mode requires you to manually start builds processing:\nWARNING: $ gitlab-runner run                       WARNING: Use sudo for system-mode:                 WARNING: $ sudo gitlab-runner...                   ```\n\nIf you're using self-managed GitLab, the coordinator URL will be http(s)://url-of-your-gitlab-instance/ci`.\n\n```text\nPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci):\nhttps://gitlab.com/ci\n","yaml",[74,413,414,419,440,465,472,478,483,492,500],{"__ignoreMap":254},[258,415,416],{"class":260,"line":261},[258,417,418],{"class":268},"$ gitlab-ci-multi-runner register\n",[258,420,422,426,429,432,434,437],{"class":260,"line":421},2,[258,423,425],{"class":424},"shJU0","WARNING",[258,427,428],{"class":360},": ",[258,430,431],{"class":424},"Running in user-mode.                     WARNING",[258,433,428],{"class":360},[258,435,436],{"class":424},"The user-mode requires you to manually start builds processing",[258,438,439],{"class":360},":\n",[258,441,443,445,447,450,452,455,458,460,462],{"class":260,"line":442},3,[258,444,425],{"class":424},[258,446,428],{"class":360},[258,448,449],{"class":424},"$ gitlab-runner run                       WARNING",[258,451,428],{"class":360},[258,453,454],{"class":424},"Use sudo for system-mode",[258,456,457],{"class":360},":                 ",[258,459,425],{"class":424},[258,461,428],{"class":360},[258,463,464],{"class":268},"$ sudo gitlab-runner...                   ```\n",[258,466,468],{"class":260,"line":467},4,[258,469,471],{"emptyLinePlaceholder":470},true,"\n",[258,473,475],{"class":260,"line":474},5,[258,476,477],{"class":268},"If you're using self-managed GitLab, the coordinator URL will be http(s)://url-of-your-gitlab-instance/ci`.\n",[258,479,481],{"class":260,"line":480},6,[258,482,471],{"emptyLinePlaceholder":470},[258,484,486,489],{"class":260,"line":485},7,[258,487,488],{"class":360},"```",[258,490,491],{"class":268},"text\n",[258,493,495,498],{"class":260,"line":494},8,[258,496,497],{"class":424},"Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci)",[258,499,439],{"class":360},[258,501,503],{"class":260,"line":502},9,[258,504,505],{"class":268},"https://gitlab.com/ci\n",[12,507,508,509,512],{},"The CI token for your project is available on GitLab's Project Settings page,\nunder ",[15,510,511],{},"Advanced Settings",". Each project has a unique token.",[249,514,519],{"className":515,"code":517,"language":518,"meta":254},[516],"language-text","Please enter the gitlab-ci token for this runner:\n\u003CCI runner token from Project > Settings > Runner>\n","text",[74,520,517],{"__ignoreMap":254},[12,522,303,523,526,527,530],{},[74,524,525],{},"register"," process suggests the name of your Mac as a description for the\nrunner. You can enter something different if you like, or just hit ",[143,528,529],{},"return"," to\ncontinue.",[249,532,535],{"className":533,"code":534,"language":518,"meta":254},[516],"Please enter the gitlab-ci description for this runner:\n[Your-Mac's-Name.local]:\n",[74,536,534],{"__ignoreMap":254},[12,538,539,540,543,544,547,548,551],{},"Enter whatever tags you'd like to further identify this particular runner. It's\nparticularly helpful when you need a particular build environment—for example,\niOS 9.2 on Xcode 7.2 on macOS 10.11 could use tags like ",[74,541,542],{},"ios_9-2",", ",[74,545,546],{},"xcode_7-2",",\nand ",[74,549,550],{},"osx_10-11",". This way, we can filter our build stages in GitLab by toolchain,\nplatform, etc.",[249,553,556],{"className":554,"code":555,"language":518,"meta":254},[516],"Please enter the gitlab-ci tags for this runner (comma separated):\nios_9-2, xcode_7-2, osx_10-11\n",[74,557,555],{"__ignoreMap":254},[12,559,560,561,564],{},"The GitLab Runner will register the runner and give it a unique ",[74,562,563],{},"runner"," ID.",[249,566,569],{"className":567,"code":568,"language":518,"meta":254},[516],"Registering runner... succeeded                     runner=s8Bgtktb\n",[74,570,568],{"__ignoreMap":254},[12,572,573,574,577,578,580],{},"The GitLab Runner has to run ",[74,575,576],{},"xcodebuild"," to build and test the project, so we\nselect ",[74,579,253],{}," as the executor:",[249,582,585],{"className":583,"code":584,"language":518,"meta":254},[516],"Please enter the executor: virtualbox, ssh, shell, parallels, docker, docker-ssh:\nshell\nRunner registered successfully. Feel free to start it, but if it's running\nalready the config should be automatically reloaded!\n",[74,586,584],{"__ignoreMap":254},[12,588,589,590,146,593,596],{},"Continue with the rest of the Runner installation instructions (",[74,591,592],{},"install",[74,594,595],{},"start","), per the documentation.",[12,598,599,600,603],{},"Go to the ",[15,601,602],{},"Runners"," page in your Project Settings and voilà:",[12,605,606],{},[135,607],{"alt":608,"src":609},"The GitLab Runner is recognized in GitLab's Project Settings.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399017/blog/Content%20Images/setting-up-gitlab-for-ios-projects/6_runner-registered.png",[12,611,612],{},"Your GitLab Runner is recognized and (almost) ready to go!",[12,614,615],{},"You can verify this by running",[249,617,619],{"className":409,"code":618,"language":411,"meta":254,"style":254},"$ gitlab-ci-multi-runner verify\nWARNING: Running in user-mode.                     WARNING: The user-mode requires you to manually start builds processing:\nWARNING: $ gitlab-runner run                       WARNING: Use sudo for system-mode:                 WARNING: $ sudo gitlab-runner...                  \nVeryfing runner... is alive                         runner=25c780b3\n",[74,620,621,626,640,664],{"__ignoreMap":254},[258,622,623],{"class":260,"line":261},[258,624,625],{"class":268},"$ gitlab-ci-multi-runner verify\n",[258,627,628,630,632,634,636,638],{"class":260,"line":421},[258,629,425],{"class":424},[258,631,428],{"class":360},[258,633,431],{"class":424},[258,635,428],{"class":360},[258,637,436],{"class":424},[258,639,439],{"class":360},[258,641,642,644,646,648,650,652,654,656,658,661],{"class":260,"line":442},[258,643,425],{"class":424},[258,645,428],{"class":360},[258,647,449],{"class":424},[258,649,428],{"class":360},[258,651,454],{"class":424},[258,653,457],{"class":360},[258,655,425],{"class":424},[258,657,428],{"class":360},[258,659,660],{"class":268},"$ sudo gitlab-runner...",[258,662,663],{"class":360},"                  \n",[258,665,666],{"class":260,"line":467},[258,667,668],{"class":268},"Veryfing runner... is alive                         runner=25c780b3\n",[12,670,671,672,675],{},"Note that they have the same ID (in this case, ",[74,673,674],{},"25c780b3",").",[12,677,678],{},"The last thing to do is to configure the build and test settings. To do so, open\nyour text editor and enter the following:",[249,680,683],{"className":681,"code":682,"language":518,"meta":254},[516],"stages:\n  - build\n\nbuild_project:\n  stage: build\n  script:\n    - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty\n    - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s\n  tags:\n    - ios_9-2\n    - xcode_7-2\n    - osx_10-11\n",[74,684,682],{"__ignoreMap":254},[12,686,687,688,690],{},"Save this file in your Xcode project folder as ",[74,689,76],{},", and don't forget\nthe period at the beginning of the file name!",[186,692,693],{},[12,694,695,698,699,701,702,705],{},[143,696,697],{},"Update:"," To clarify, the ",[74,700,76],{}," file should go in the folder you created for your iOS project, which is also typically where your Xcode project file (",[74,703,704],{},"ProjectName.xcodeproj",") is found. Thanks to commenter Palo for pointing this out!",[12,707,708],{},"Let's go through the file with some detail:",[111,710,711,729,738],{},[61,712,713,714,717,718,721,722,725,726,675],{},"The file first describes the ",[74,715,716],{},"stages"," available to each ",[74,719,720],{},"job",". For simplicity,\nwe have one stage (",[74,723,724],{},"build",") and one job (",[74,727,728],{},"build_project",[61,730,731,732,734,735,737],{},"The file then provides the settings for each ",[74,733,720],{},". The ",[74,736,728],{}," job runs\ntwo scripts: one to clean the Xcode project, and then another to build and test\nit. You can probably skip the cleaning script to save time, unless you want to be\nsure that you're building from a clean state.",[61,739,740,741,744],{},"Under ",[74,742,743],{},"tags",", add the tags you created when you registered the GitLab Runner.",[12,746,747],{},"There are also some things to look out for:",[111,749,750,763,774],{},[61,751,752,753,756,757,760,761,675],{},"Make sure to replace all references to ",[74,754,755],{},"ProjectName"," with the name of your\nXcode project; if you're using a different scheme than the default, then make\nsure you pass in the proper ",[74,758,759],{},"SchemeName"," too (the default is the same as the\n",[74,762,755],{},[61,764,765,766,769,770,773],{},"In the ",[74,767,768],{},"xcodebuild test"," command, notice the ",[74,771,772],{},"-destination"," option is set to\nlaunch an iPhone 6S image running iOS 9.2 in the Simulator; if you want to run a\ndifferent device (iPad, for example), you'll need to change this.",[61,775,776,777,782,783,786,787,790,791,794],{},"If you're using a workspace rather than a project (e.g., because your app uses ",[19,778,781],{"href":779,"rel":780},"https://cocoapods.org",[],"Cocoapods","), change the ",[74,784,785],{},"-project ProjectName.xcodeproj","\noptions to ",[74,788,789],{},"-workspace WorkspaceName.xcworkspace",". There are several options\navailable to customize your build; run ",[74,792,793],{},"xcodebuild --help"," in the Terminal to\nexplore these further.",[12,796,797,798,800,801,804],{},"There's a simple tool for \"linting\" (i.e., validating) your ",[74,799,76],{}," in\nGitLab. From your GitLab project page, click on CI/CD > Jobs in the sidebar, then in\nthe upper-right corner, click on ",[143,802,803],{},"CI lint",":",[12,806,807],{},[135,808],{"alt":809,"src":810},"Accessing the GitLab CI script linter.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399018/blog/Content%20Images/setting-up-gitlab-for-ios-projects/7_ci-lint-button.png",[12,812,813,814,816,817,820],{},"Paste the contents of your ",[74,815,76],{}," file into the text box and click on\n",[143,818,819],{},"Validate",". You should see something like:",[186,822,823],{},[12,824,825,828],{},[143,826,827],{},"Status:"," syntax is correct",[12,830,831],{},"This won't tell you if your project name or the Simulator chosen is correct, so\nbe sure to double-check these settings.",[12,833,303,834,836],{},[74,835,76],{}," file is extremely customizable. You can limit jobs to run\non success or failure, or depending on branches or tags, etc.—read through\nthe documentation to get a feeling\nfor just how flexible and powerful it is.",[35,838,840],{"id":839},"setting-up-your-gitlab-project-for-ci","Setting up your GitLab project for CI",[12,842,843,844,847,848,851],{},"Actually, there's really not much to do here! CI is enabled by default on new\nprojects. If your iOS project has some environment variables you want to keep\nsecret, but you want to keep the project public on GitLab, you may want to disable\n",[143,845,846],{},"Public builds"," in Project Settings, under ",[15,849,850],{},"Continuous Integration",". This will\nhide the build results from everyone except members of the project.",[12,853,854,855,857,858,861],{},"You may also want to go to ",[15,856,602],{}," under your Project Settings and click\n",[143,859,860],{},"Disable shared runners",", as they're not needed anyhow—we're using a\nproject-specific runner.",[12,863,864],{},"We're now ready to trigger a CI build!",[35,866,868],{"id":867},"how-to-trigger-builds","How to trigger builds",[12,870,871],{},"To trigger a build, all you have to do is push a commit to GitLab. From the Terminal:",[249,873,875],{"className":251,"code":874,"language":253,"meta":254,"style":254},"$ git add .\n$ git commit -m \"First commit.\"\n[...commit info...]\n$ git push origin master\n[...push info...]\n",[74,876,877,888,903,908,922],{"__ignoreMap":254},[258,878,879,881,883,885],{"class":260,"line":261},[258,880,265],{"class":264},[258,882,338],{"class":268},[258,884,344],{"class":268},[258,886,887],{"class":268}," .\n",[258,889,890,892,894,897,900],{"class":260,"line":421},[258,891,265],{"class":264},[258,893,338],{"class":268},[258,895,896],{"class":268}," commit",[258,898,899],{"class":272}," -m",[258,901,902],{"class":268}," \"First commit.\"\n",[258,904,905],{"class":260,"line":442},[258,906,907],{"class":360},"[...commit info...]\n",[258,909,910,912,914,917,919],{"class":260,"line":467},[258,911,265],{"class":264},[258,913,338],{"class":268},[258,915,916],{"class":268}," push",[258,918,347],{"class":268},[258,920,921],{"class":268}," master\n",[258,923,924],{"class":260,"line":474},[258,925,926],{"class":360},"[...push info...]\n",[12,928,929],{},"If everything worked, and you installed the GitLab Runner on the same machine,\nyou'll notice that Simulator launches, installs your iOS app and launches it, and\nthen goes back to the home screen.",[12,931,599,932,935],{},[15,933,934],{},"Builds"," page of your GitLab project and have a look at the results!",[12,937,938],{},[135,939],{"alt":940,"src":941},"The Build page after your first CI build.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399019/blog/Content%20Images/setting-up-gitlab-for-ios-projects/8_build-page-success.png",[12,943,215,944,948],{},[258,945,947],{"style":946},"color:green","✔︎ success"," button to see the build output:",[12,950,951],{},[135,952],{"alt":953,"src":954},"The build results page.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399019/blog/Content%20Images/setting-up-gitlab-for-ios-projects/9_build-results.png",[12,956,957,958,960],{},"Here you'll see the output from all the steps you requested in your ",[74,959,76],{},"\nfile. At the bottom of the log, you should see something like:",[249,962,965],{"className":963,"code":964,"language":518,"meta":254},[516],"All tests\nTest Suite GitLab-CI-for-iOSTests.xctest started\nGitLab_CI_for_iOSTests\n    . testExample (0.001 seconds)\n    T testPerformanceExample measured (0.000 seconds)\n    . testPerformanceExample (0.324 seconds)\n\n\n     Executed 2 tests, with 0 failures (0 unexpected) in 0.325 (0.328) seconds\n\nAll tests\nTest Suite GitLab-CI-for-iOSUITests.xctest started\nGitLab_CI_for_iOSUITests\n    . testExample (3.587 seconds)\n\n\n     Executed 1 test, with 0 failures (0 unexpected) in 3.587 (3.589) seconds\n\n\nBuild succeeded.\n",[74,966,964],{"__ignoreMap":254},[12,968,969],{},"Now you can go ahead and start writing tests for your code, and every time you\npush a commit, GitLab will diligently fetch the project, clean it, and then build\nand test it. If the build fails, you can take action to fix the commit.",[35,971,973],{"id":972},"starting-and-stopping-the-runner-on-your-mac","Starting and stopping the runner on your Mac",[12,975,976],{},"The GitLab Runner includes several convenient commands, which you can list easily:",[249,978,981],{"className":979,"code":980,"language":518,"meta":254},[516],"$ gitlab-ci-multi-runner --help\nNAME:\n   gitlab-ci-multi-runner - a GitLab Runner\n\nUSAGE:\n   gitlab-ci-multi-runner [global options] command [command options] [arguments...]\n\nVERSION:\n   1.0.4 (014aa8c)\n\nAUTHOR(S):\n   Kamil Trzciński \u003Cayufan@ayufan.eu>\n\nCOMMANDS:\n   archive  find and archive files (internal)\n   artifacts    upload build artifacts (internal)\n   extract  extract files from an archive (internal)\n   exec     execute a build locally\n   list     List all configured runners\n   run      run multi runner service\n   register register a new runner\n   install  install service\n   uninstall    uninstall service\n   start    start service\n   stop     stop service\n   restart  restart service\n   status   get status of a service\n   run-single   start single runner\n   unregister   unregister specific runner\n   verify   verify all registered runners\n   help, h  Shows a list of commands or help for one command\n\nGLOBAL OPTIONS:\n   --debug          debug mode [$DEBUG]\n   --log-level, -l \"info\"   Log level (options: debug, info, warn, error, fatal, panic)\n   --help, -h           show help\n   --version, -v        print the version\n",[74,982,980],{"__ignoreMap":254},[12,984,985],{},"You may want to stop the Runner so that a build isn't immediately triggered by a\npushed commit:",[249,987,990],{"className":988,"code":989,"language":518,"meta":254},[516],"$ gitlab-ci-multi-runner stop\n$ gitlab-ci-multi-runner status\ngitlab-runner: Service is not running.\n",[74,991,989],{"__ignoreMap":254},[12,993,994,995,998],{},"In this case, any builds pushed will show up as ",[143,996,997],{},"pending"," and will be triggered\nas soon as you restart the Runner:",[249,1000,1003],{"className":1001,"code":1002,"language":518,"meta":254},[516],"$ gitlab-ci-multi-runner start\n$ gitlab-ci-multi-runner status\ngitlab-runner: Service is running!\n",[74,1004,1002],{"__ignoreMap":254},[12,1006,1007],{},"Any pending builds in the queue will then be triggered, launching Simulator and\nrunning the test suite normally.",[35,1009,1011],{"id":1010},"advanced-archiving-the-project-automatically","Advanced: archiving the project automatically",[12,1013,1014,1015,1018],{},"Let's say that, if we commit to the ",[74,1016,1017],{},"master"," branch, we want GitLab CI to not only build and\ntest the project, but also provide some continuous delivery, where it creates an\napplication archive, and uploads it to GitLab.",[12,1020,1021,1022,1024,1025,1028,1029,1032],{},"We start by modifying our ",[74,1023,76],{}," file to add an ",[74,1026,1027],{},"archive"," stage and an\n",[74,1030,1031],{},"archive_project"," job:",[249,1034,1037],{"className":1035,"code":1036,"language":518,"meta":254},[516],"stages:\n  - build\n  - archive\n\nbuild_project:\n  stage: build\n  script:\n    - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty\n    - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s\n  tags:\n    - ios_9-2\n    - xcode_7-2\n    - osx_10-11\n\narchive_project:\n  stage: archive\n  script:\n    - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName\n    - xcodebuild -exportArchive -exportFormat ipa -archivePath \"build/ProjectName.xcarchive\" -exportPath \"build/ProjectName.ipa\" -exportProvisioningProfile \"ProvisioningProfileName\"\n  only:\n    - master\n  artifacts:\n    paths:\n    - build/ProjectName.ipa\n  tags:\n    - ios_9-2\n    - xcode_7-2\n    - osx_10-11\n",[74,1038,1036],{"__ignoreMap":254},[12,1040,303,1041,1043,1044,1047,1048,1051,1052,1055,1056,1059,1060,1063],{},[74,1042,1031],{}," job runs two scripts: the first cleans and archives your\nXcode project, and the second builds an ",[74,1045,1046],{},".ipa"," file; the ",[74,1049,1050],{},"only"," setting means that\nthis job will only run when we commit to master. Notice that it also defines\n",[74,1053,1054],{},"artifacts","; after the ",[74,1057,1058],{},"ProjectName.ipa"," application archive is created, this\noption uploads it to GitLab, where you can later download it from the ",[15,1061,1062],{},"Build"," page.",[12,1065,765,1066,1068,1069,1072,1073,1076,1077,1079],{},[74,1067,1031],{}," job's ",[74,1070,1071],{},"exportArchive"," script, make sure you pass in the\ncorrect ",[74,1074,1075],{},"ProvisioningProfileName",". It's possible that the ",[74,1078,1031],{}," job\nwill fail if your developer keys are in the login Keychain, because it's not unlocked\nin the script. The simplest way to fix this without putting your password in a\nscript is to open Keychain Access on your Mac and drag and drop them to the System\nKeychain.",[12,1081,1082],{},"Now, when we commit to master, the build will also show us the archive results,\nalong with the option to download or browse our build artifacts!",[12,1084,1085],{},[135,1086],{"alt":1087,"src":1088},"The archive results page.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782399016/blog/Content%20Images/setting-up-gitlab-for-ios-projects/10_archive-results.png",[35,1090,1092],{"id":1091},"other-salient-points","Other salient points",[111,1094,1095,1105,1112,1115,1118],{},[61,1096,1097,1098,1101,1102,1104],{},"This workflow should work for ",[15,1099,1100],{},"any"," kind of Xcode project, including tvOS,\nwatchOS, and macOS. Just be sure to specify the appropriate Simulator device in\nyour ",[74,1103,76],{}," file.",[61,1106,1107,1108,1111],{},"If you want to push a commit but don't want to trigger a CI build, simply add\n",[74,1109,1110],{},"[ci skip]"," to your commit message.",[61,1113,1114],{},"If the user that installed the GitLab runner isn't logged in, the runner won't\nrun. So, if builds seem to be pending for a long time, you may want to check on\nthis!",[61,1116,1117],{},"If you're working on a team, or if your project is public, you may want to install\nthe GitLab Runner on a dedicated build machine. It can otherwise be very distracting\nto be using your machine and have Simulator launch unexpectedly to run a test suite.",[61,1119,1120,1121,1126,1127,1130,1131,1136,1137,1139],{},"The test project used in this particular tutorial is ",[19,1122,1125],{"href":1123,"rel":1124},"https://gitlab.com/AngeloStavrow/gitlab-ci-for-ios-projects",[],"available here",",\nbut the Runner is permanently stopped. Note that the project isn't tied to a particular\nteam, so provisioning isn't an issue here; in fact, ",[15,1128,1129],{},"no"," provisioning profile is specified.\nYou, however, may need to ",[19,1132,1135],{"href":1133,"rel":1134},"https://coderwall.com/p/rv2lgw/use-xcodebuild-to-build-workspace-vs-project",[],"add some parameters to the build scripts","\nin your ",[74,1138,76],{}," file if you see provisioning errors in your build output.",[1141,1142,1144],"h2",{"id":1143},"about-guest-author-angelo-stavrow","About guest author Angelo Stavrow",[12,1146,1147,1152,1153,1156],{},[19,1148,1151],{"href":1149,"rel":1150},"http://angelostavrow.com",[],"Angelo"," is a Quality Engineer and Software Developer\nliving in Montreal, Canada. He believes that open, honest, collaboration is the\nbest path towards building great things ",[15,1154,1155],{},"and"," great teams.",[1158,1159,1160],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}",{"title":254,"searchDepth":421,"depth":421,"links":1162},[1163,1164,1165,1166,1167,1168,1169,1170,1171,1172],{"id":37,"depth":442,"text":38},{"id":95,"depth":442,"text":96},{"id":127,"depth":442,"text":128},{"id":392,"depth":442,"text":393},{"id":839,"depth":442,"text":840},{"id":867,"depth":442,"text":868},{"id":972,"depth":442,"text":973},{"id":1010,"depth":442,"text":1011},{"id":1091,"depth":442,"text":1092},{"id":1143,"depth":421,"text":1144},"engineering","2016-03-10","Learn how to set up GitLab CI for your iOS projects.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749684195/Blog/Hero%20Images/ios-development.jpg",{"excerpt":1181},{"type":9,"value":1182},[1183,1193],[12,1184,1185],{},[15,1186,17,1187,24,1190,30],{},[19,1188,23],{"href":21,"rel":1189},[],[19,1191,29],{"href":27,"rel":1192},[],[12,1194,33],{},"/en-us/blog/setting-up-gitlab-ci-for-ios-projects",{"title":5,"description":1175,"ogTitle":5,"ogDescription":1175,"noIndex":1178,"ogImage":1179,"ogUrl":1197,"ogSiteName":1198,"ogType":1199,"canonicalUrls":1197},"https://about.gitlab.com/blog/setting-up-gitlab-ci-for-ios-projects","https://about.gitlab.com","article","setting-up-gitlab-ci-for-ios-projects","en-us/blog/setting-up-gitlab-ci-for-ios-projects","BlogPost","HxXasGjf_kV6NHiXHML-uli7IwOnu-5z8Kq1Rtudbpg",{"logo":1205,"freeTrial":1209,"sales":1214,"login":1219,"items":1224,"search":1548,"minimal":1578,"duo":1597,"switchNav":1606,"pricingDeployment":1617},{"config":1206},{"href":367,"dataGaName":1207,"dataGaLocation":1208},"gitlab logo","header",{"text":1210,"config":1211},"Get free trial",{"href":1212,"dataGaName":1213,"dataGaLocation":1208},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1215,"config":1216},"Request a demo",{"href":1217,"dataGaName":1218,"dataGaLocation":1208},"/sales/?contact-topic=request-demo","sales",{"text":1220,"config":1221},"Sign in",{"href":1222,"dataGaName":1223,"dataGaLocation":1208},"https://gitlab.com/users/sign_in/","sign in",[1225,1254,1352,1357,1470,1526],{"text":1226,"config":1227,"menu":1229},"Platform",{"dataNavLevelOne":1228},"platform",{"type":1230,"columns":1231},"cards",[1232,1238,1246],{"title":1226,"description":1233,"link":1234},"The intelligent orchestration platform for DevSecOps",{"text":1235,"config":1236},"Explore our Platform",{"href":1237,"dataGaName":1228,"dataGaLocation":1208},"/platform/",{"title":1239,"description":1240,"link":1241},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1242,"config":1243},"Meet GitLab Duo",{"href":1244,"dataGaName":1245,"dataGaLocation":1208},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1247,"description":1248,"link":1249},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1250,"config":1251},"Learn more",{"href":1252,"dataGaName":1253,"dataGaLocation":1208},"/why-gitlab/","why gitlab",{"text":199,"left":470,"config":1255,"menu":1257},{"dataNavLevelOne":1256},"solutions",{"type":1258,"link":1259,"columns":1263,"feature":1331},"lists",{"text":1260,"config":1261},"View all Solutions",{"href":1262,"dataGaName":1256,"dataGaLocation":1208},"/solutions/",[1264,1287,1310],{"title":1265,"description":1266,"link":1267,"items":1272},"Automation","CI/CD and automation to accelerate deployment",{"config":1268},{"icon":1269,"href":1270,"dataGaName":1271,"dataGaLocation":1208},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1273,1276,1279,1283],{"text":1274,"config":1275},"CI/CD",{"href":51,"dataGaLocation":1208,"dataGaName":1274},{"text":1239,"config":1277},{"href":1244,"dataGaLocation":1208,"dataGaName":1278},"gitlab duo agent platform - product menu",{"text":1280,"config":1281},"Source Code Management",{"href":1282,"dataGaLocation":1208,"dataGaName":1280},"/solutions/source-code-management/",{"text":1284,"config":1285},"Automated Software Delivery",{"href":1270,"dataGaLocation":1208,"dataGaName":1286},"Automated software delivery",{"title":1288,"description":1289,"link":1290,"items":1295},"Security","Deliver code faster without compromising security",{"config":1291},{"href":1292,"dataGaName":1293,"dataGaLocation":1208,"icon":1294},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1296,1300,1305],{"text":1297,"config":1298},"Application Security Testing",{"href":1292,"dataGaName":1299,"dataGaLocation":1208},"Application security testing",{"text":1301,"config":1302},"Software Supply Chain Security",{"href":1303,"dataGaLocation":1208,"dataGaName":1304},"/solutions/supply-chain/","Software supply chain security",{"text":1306,"config":1307},"Software Compliance",{"href":1308,"dataGaName":1309,"dataGaLocation":1208},"/solutions/software-compliance/","software compliance",{"title":1311,"link":1312,"items":1317},"Measurement",{"config":1313},{"icon":1314,"href":1315,"dataGaName":1316,"dataGaLocation":1208},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1318,1322,1326],{"text":1319,"config":1320},"Visibility & Measurement",{"href":1315,"dataGaLocation":1208,"dataGaName":1321},"Visibility and Measurement",{"text":1323,"config":1324},"Value Stream Management",{"href":1325,"dataGaLocation":1208,"dataGaName":1323},"/solutions/value-stream-management/",{"text":1327,"config":1328},"Analytics & Insights",{"href":1329,"dataGaLocation":1208,"dataGaName":1330},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1332,"type":1258,"items":1333},"GitLab for",[1334,1340,1346],{"text":1335,"config":1336},"Enterprise",{"icon":1337,"href":1338,"dataGaLocation":1208,"dataGaName":1339},"Building","/enterprise/","enterprise",{"text":1341,"config":1342},"Small Business",{"icon":1343,"href":1344,"dataGaLocation":1208,"dataGaName":1345},"Work","/small-business/","small business",{"text":1347,"config":1348},"Public Sector",{"icon":1349,"href":1350,"dataGaLocation":1208,"dataGaName":1351},"Organization","/solutions/public-sector/","public sector",{"text":1353,"config":1354},"Pricing",{"href":1355,"dataGaName":1356,"dataGaLocation":1208,"dataNavLevelOne":1356},"/pricing/","pricing",{"text":1358,"config":1359,"menu":1361},"Resources",{"dataNavLevelOne":1360},"resources",{"type":1258,"link":1362,"columns":1366,"feature":1459},{"text":1363,"config":1364},"View all resources",{"href":1365,"dataGaName":1360,"dataGaLocation":1208},"/resources/",[1367,1399,1426],{"title":1368,"items":1369},"Getting started",[1370,1374,1379,1384,1389,1394],{"text":1371,"config":1372},"Install",{"href":1373,"dataGaName":592,"dataGaLocation":1208},"/install/",{"text":1375,"config":1376},"Quick start guides",{"href":1377,"dataGaName":1378,"dataGaLocation":1208},"/get-started/","quick setup checklists",{"text":1380,"config":1381},"Learn",{"href":1382,"dataGaLocation":1208,"dataGaName":1383},"https://university.gitlab.com/","learn",{"text":1385,"config":1386},"Product documentation",{"href":1387,"dataGaName":1388,"dataGaLocation":1208},"https://docs.gitlab.com/","product documentation",{"text":1390,"config":1391},"Best practice videos",{"href":1392,"dataGaName":1393,"dataGaLocation":1208},"/getting-started-videos/","best practice videos",{"text":1395,"config":1396},"Integrations",{"href":1397,"dataGaName":1398,"dataGaLocation":1208},"/integrations/","integrations",{"title":1400,"items":1401},"Discover",[1402,1407,1412,1417,1421],{"text":1403,"config":1404},"Customer success stories",{"href":1405,"dataGaName":1406,"dataGaLocation":1208},"/customers/","customer success stories",{"text":1408,"config":1409},"Blog",{"href":1410,"dataGaName":1411,"dataGaLocation":1208},"/blog/","blog",{"text":1413,"config":1414},"Demo Hub",{"href":1415,"dataGaName":1416,"dataGaLocation":1208},"/demo-hub/","demo hub",{"text":1418,"config":1419},"The Source",{"href":1420,"dataGaName":1411,"dataGaLocation":1208},"/the-source/",{"text":1422,"config":1423},"Remote",{"href":1424,"dataGaName":1425,"dataGaLocation":1208},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1427,"items":1428},"Connect",[1429,1434,1439,1444,1449,1454],{"text":1430,"config":1431},"GitLab Services",{"href":1432,"dataGaName":1433,"dataGaLocation":1208},"/services/","services",{"text":1435,"config":1436},"Contribute",{"href":1437,"dataGaName":1438,"dataGaLocation":1208},"https://contributors.gitlab.com","contribute",{"text":1440,"config":1441},"Community",{"href":1442,"dataGaName":1443,"dataGaLocation":1208},"/community/","community",{"text":1445,"config":1446},"Forum",{"href":1447,"dataGaName":1448,"dataGaLocation":1208},"https://forum.gitlab.com/","forum",{"text":1450,"config":1451},"Events",{"href":1452,"dataGaName":1453,"dataGaLocation":1208},"/events/","events",{"text":1455,"config":1456},"Partners",{"href":1457,"dataGaName":1458,"dataGaLocation":1208},"/partners/","partners",{"config":1460,"title":1463,"text":1464,"link":1465},{"background":1461,"textColor":1462},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":1466,"config":1467},"Read the latest",{"href":1468,"dataGaName":1469,"dataGaLocation":1208},"/whats-new/","whats new",{"text":1471,"config":1472,"menu":1474},"Company",{"dataNavLevelOne":1473},"company",{"type":1258,"columns":1475},[1476],{"items":1477},[1478,1483,1489,1491,1496,1501,1506,1511,1516,1521],{"text":1479,"config":1480},"About",{"href":1481,"dataGaName":1482,"dataGaLocation":1208},"/company/","about",{"text":1484,"config":1485,"footerGa":1488},"Jobs",{"href":1486,"dataGaName":1487,"dataGaLocation":1208},"/jobs/","jobs",{"dataGaName":1487},{"text":1450,"config":1490},{"href":1452,"dataGaName":1453,"dataGaLocation":1208},{"text":1492,"config":1493},"Leadership",{"href":1494,"dataGaName":1495,"dataGaLocation":1208},"/company/team/e-group/","leadership",{"text":1497,"config":1498},"Handbook",{"href":1499,"dataGaName":1500,"dataGaLocation":1208},"https://handbook.gitlab.com/","handbook",{"text":1502,"config":1503},"Investor relations",{"href":1504,"dataGaName":1505,"dataGaLocation":1208},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1507,"config":1508},"Trust Center",{"href":1509,"dataGaName":1510,"dataGaLocation":1208},"/security/","trust center",{"text":1512,"config":1513},"AI Transparency Center",{"href":1514,"dataGaName":1515,"dataGaLocation":1208},"/ai-transparency-center/","ai transparency center",{"text":1517,"config":1518},"Newsletter",{"href":1519,"dataGaName":1520,"dataGaLocation":1208},"/company/contact/#contact-forms","newsletter",{"text":1522,"config":1523},"Press",{"href":1524,"dataGaName":1525,"dataGaLocation":1208},"/press/","press",{"text":1527,"config":1528,"menu":1529},"Contact us",{"dataNavLevelOne":1473},{"type":1258,"columns":1530},[1531],{"items":1532},[1533,1538,1543],{"text":1534,"config":1535},"Talk to sales",{"href":1536,"dataGaName":1537,"dataGaLocation":1208},"/sales/","talk to sales",{"text":1539,"config":1540},"Support portal",{"href":1541,"dataGaName":1542,"dataGaLocation":1208},"https://support.gitlab.com/hc/en-us","support portal",{"text":1544,"config":1545},"Customer portal",{"href":1546,"dataGaName":1547,"dataGaLocation":1208},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":218,"login":1549,"suggestions":1556},{"text":1550,"link":1551},"To search repositories and projects, login to",{"text":1552,"config":1553},"gitlab.com",{"href":1222,"dataGaName":1554,"dataGaLocation":1555},"search login","search",{"text":1557,"default":1558},"Suggestions",[1559,1561,1565,1567,1571,1575],{"text":1239,"config":1560},{"href":1244,"dataGaName":1239,"dataGaLocation":1555},{"text":1562,"config":1563},"Code Suggestions (AI)",{"href":1564,"dataGaName":1562,"dataGaLocation":1555},"/solutions/code-suggestions/",{"text":1274,"config":1566},{"href":51,"dataGaName":1274,"dataGaLocation":1555},{"text":1568,"config":1569},"GitLab on AWS",{"href":1570,"dataGaName":1568,"dataGaLocation":1555},"/partners/technology-partners/aws/",{"text":1572,"config":1573},"GitLab on Google Cloud",{"href":1574,"dataGaName":1572,"dataGaLocation":1555},"/partners/technology-partners/google-cloud-platform/",{"text":1576,"config":1577},"Why GitLab?",{"href":1252,"dataGaName":1576,"dataGaLocation":1555},{"freeTrial":1579,"mobileIcon":1584,"desktopIcon":1589,"secondaryButton":1592},{"text":1580,"config":1581},"Start free trial",{"href":1582,"dataGaName":1213,"dataGaLocation":1583},"https://gitlab.com/-/trials/new/","nav",{"altText":1585,"config":1586},"Gitlab Icon",{"src":1587,"dataGaName":1588,"dataGaLocation":1583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1585,"config":1590},{"src":1591,"dataGaName":1588,"dataGaLocation":1583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1593,"config":1594},"Get Started",{"href":1595,"dataGaName":1596,"dataGaLocation":1583},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1598,"mobileIcon":1602,"desktopIcon":1604},{"text":1599,"config":1600},"Learn more about GitLab Duo",{"href":1244,"dataGaName":1601,"dataGaLocation":1583},"gitlab duo",{"altText":1585,"config":1603},{"src":1587,"dataGaName":1588,"dataGaLocation":1583},{"altText":1585,"config":1605},{"src":1591,"dataGaName":1588,"dataGaLocation":1583},{"button":1607,"mobileIcon":1612,"desktopIcon":1614},{"text":1608,"config":1609},"/switch",{"href":1610,"dataGaName":1611,"dataGaLocation":1583},"#contact","switch",{"altText":1585,"config":1613},{"src":1587,"dataGaName":1588,"dataGaLocation":1583},{"altText":1585,"config":1615},{"src":1616,"dataGaName":1588,"dataGaLocation":1583},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1618,"mobileIcon":1623,"desktopIcon":1625},{"text":1619,"config":1620},"Back to pricing",{"href":1355,"dataGaName":1621,"dataGaLocation":1583,"icon":1622},"back to pricing","GoBack",{"altText":1585,"config":1624},{"src":1587,"dataGaName":1588,"dataGaLocation":1583},{"altText":1585,"config":1626},{"src":1591,"dataGaName":1588,"dataGaLocation":1583},{"title":1628,"titleMobile":1629,"button":1630,"config":1635},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1250,"config":1631},{"href":1632,"dataGaName":1633,"dataGaLocation":1634},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1636,"disabled":1178},"release",{"data":1638},{"text":1639,"source":1640,"edit":1646,"contribute":1651,"config":1656,"items":1661,"minimal":1870},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1641,"config":1642},"View page source",{"href":1643,"dataGaName":1644,"dataGaLocation":1645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1647,"config":1648},"Edit this page",{"href":1649,"dataGaName":1650,"dataGaLocation":1645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1652,"config":1653},"Please contribute",{"href":1654,"dataGaName":1655,"dataGaLocation":1645},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1657,"facebook":1658,"youtube":1659,"linkedin":1660},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1662,1709,1762,1806,1838],{"title":1353,"links":1663,"subMenu":1678},[1664,1668,1673],{"text":1665,"config":1666},"View plans",{"href":1355,"dataGaName":1667,"dataGaLocation":1645},"view plans",{"text":1669,"config":1670},"Why Premium?",{"href":1671,"dataGaName":1672,"dataGaLocation":1645},"/pricing/premium/","why premium",{"text":1674,"config":1675},"Why Ultimate?",{"href":1676,"dataGaName":1677,"dataGaLocation":1645},"/pricing/ultimate/","why ultimate",[1679],{"title":1680,"links":1681},"Contact Us",[1682,1685,1687,1689,1694,1699,1704],{"text":1683,"config":1684},"Contact sales",{"href":1536,"dataGaName":1218,"dataGaLocation":1645},{"text":1539,"config":1686},{"href":1541,"dataGaName":1542,"dataGaLocation":1645},{"text":1544,"config":1688},{"href":1546,"dataGaName":1547,"dataGaLocation":1645},{"text":1690,"config":1691},"Status",{"href":1692,"dataGaName":1693,"dataGaLocation":1645},"https://status.gitlab.com/","status",{"text":1695,"config":1696},"Terms of use",{"href":1697,"dataGaName":1698,"dataGaLocation":1645},"/terms/","terms of use",{"text":1700,"config":1701},"Privacy statement",{"href":1702,"dataGaName":1703,"dataGaLocation":1645},"/privacy/","privacy statement",{"text":1705,"config":1706},"Cookie preferences",{"dataGaName":1707,"dataGaLocation":1645,"id":1708,"isOneTrustButton":470},"cookie preferences","ot-sdk-btn",{"title":199,"links":1710,"subMenu":1719},[1711,1715],{"text":1712,"config":1713},"DevSecOps platform",{"href":1237,"dataGaName":1714,"dataGaLocation":1645},"devsecops platform",{"text":1716,"config":1717},"AI-Assisted Development",{"href":1244,"dataGaName":1718,"dataGaLocation":1645},"ai-assisted development",[1720],{"title":1721,"links":1722},"Topics",[1723,1727,1732,1737,1742,1747,1752,1757],{"text":1724,"config":1725},"CICD",{"href":43,"dataGaName":1726,"dataGaLocation":1645},"cicd",{"text":1728,"config":1729},"GitOps",{"href":1730,"dataGaName":1731,"dataGaLocation":1645},"/topics/gitops/","gitops",{"text":1733,"config":1734},"DevOps",{"href":1735,"dataGaName":1736,"dataGaLocation":1645},"/topics/devops/","devops",{"text":1738,"config":1739},"Version Control",{"href":1740,"dataGaName":1741,"dataGaLocation":1645},"/topics/version-control/","version control",{"text":1743,"config":1744},"DevSecOps",{"href":1745,"dataGaName":1746,"dataGaLocation":1645},"/topics/devsecops/","devsecops",{"text":1748,"config":1749},"Cloud Native",{"href":1750,"dataGaName":1751,"dataGaLocation":1645},"/topics/cloud-native/","cloud native",{"text":1753,"config":1754},"AI for Coding",{"href":1755,"dataGaName":1756,"dataGaLocation":1645},"/topics/devops/ai-for-coding/","ai for coding",{"text":1758,"config":1759},"Agentic AI",{"href":1760,"dataGaName":1761,"dataGaLocation":1645},"/topics/agentic-ai/","agentic ai",{"title":1763,"links":1764},"Solutions",[1765,1767,1769,1774,1778,1781,1785,1788,1790,1793,1796,1801],{"text":1297,"config":1766},{"href":1292,"dataGaName":1297,"dataGaLocation":1645},{"text":1286,"config":1768},{"href":1270,"dataGaName":1271,"dataGaLocation":1645},{"text":1770,"config":1771},"Agile development",{"href":1772,"dataGaName":1773,"dataGaLocation":1645},"/solutions/agile-delivery/","agile delivery",{"text":1775,"config":1776},"SCM",{"href":1282,"dataGaName":1777,"dataGaLocation":1645},"source code management",{"text":1724,"config":1779},{"href":51,"dataGaName":1780,"dataGaLocation":1645},"continuous integration & delivery",{"text":1782,"config":1783},"Value stream management",{"href":1325,"dataGaName":1784,"dataGaLocation":1645},"value stream management",{"text":1728,"config":1786},{"href":1787,"dataGaName":1731,"dataGaLocation":1645},"/solutions/gitops/",{"text":1335,"config":1789},{"href":1338,"dataGaName":1339,"dataGaLocation":1645},{"text":1791,"config":1792},"Small business",{"href":1344,"dataGaName":1345,"dataGaLocation":1645},{"text":1794,"config":1795},"Public sector",{"href":1350,"dataGaName":1351,"dataGaLocation":1645},{"text":1797,"config":1798},"Education",{"href":1799,"dataGaName":1800,"dataGaLocation":1645},"/solutions/education/","education",{"text":1802,"config":1803},"Financial services",{"href":1804,"dataGaName":1805,"dataGaLocation":1645},"/solutions/finance/","financial services",{"title":1358,"links":1807},[1808,1810,1812,1814,1817,1819,1822,1824,1826,1828,1830,1832,1834,1836],{"text":1371,"config":1809},{"href":1373,"dataGaName":592,"dataGaLocation":1645},{"text":1375,"config":1811},{"href":1377,"dataGaName":1378,"dataGaLocation":1645},{"text":1380,"config":1813},{"href":1382,"dataGaName":1383,"dataGaLocation":1645},{"text":1385,"config":1815},{"href":1387,"dataGaName":1816,"dataGaLocation":1645},"docs",{"text":1408,"config":1818},{"href":1410,"dataGaName":1411,"dataGaLocation":1645},{"text":1820,"config":1821},"What's new",{"href":1468,"dataGaName":1469,"dataGaLocation":1645},{"text":1403,"config":1823},{"href":1405,"dataGaName":1406,"dataGaLocation":1645},{"text":1422,"config":1825},{"href":1424,"dataGaName":1425,"dataGaLocation":1645},{"text":1430,"config":1827},{"href":1432,"dataGaName":1433,"dataGaLocation":1645},{"text":1435,"config":1829},{"href":1437,"dataGaName":1438,"dataGaLocation":1645},{"text":1440,"config":1831},{"href":1442,"dataGaName":1443,"dataGaLocation":1645},{"text":1445,"config":1833},{"href":1447,"dataGaName":1448,"dataGaLocation":1645},{"text":1450,"config":1835},{"href":1452,"dataGaName":1453,"dataGaLocation":1645},{"text":1455,"config":1837},{"href":1457,"dataGaName":1458,"dataGaLocation":1645},{"title":1471,"links":1839},[1840,1842,1844,1846,1848,1850,1854,1859,1861,1863,1865],{"text":1479,"config":1841},{"href":1481,"dataGaName":1473,"dataGaLocation":1645},{"text":1484,"config":1843},{"href":1486,"dataGaName":1487,"dataGaLocation":1645},{"text":1492,"config":1845},{"href":1494,"dataGaName":1495,"dataGaLocation":1645},{"text":1497,"config":1847},{"href":1499,"dataGaName":1500,"dataGaLocation":1645},{"text":1502,"config":1849},{"href":1504,"dataGaName":1505,"dataGaLocation":1645},{"text":1851,"config":1852},"Sustainability",{"href":1853,"dataGaName":1851,"dataGaLocation":1645},"/sustainability/",{"text":1855,"config":1856},"Diversity, inclusion and belonging (DIB)",{"href":1857,"dataGaName":1858,"dataGaLocation":1645},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1507,"config":1860},{"href":1509,"dataGaName":1510,"dataGaLocation":1645},{"text":1517,"config":1862},{"href":1519,"dataGaName":1520,"dataGaLocation":1645},{"text":1522,"config":1864},{"href":1524,"dataGaName":1525,"dataGaLocation":1645},{"text":1866,"config":1867},"Modern Slavery Transparency Statement",{"href":1868,"dataGaName":1869,"dataGaLocation":1645},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1871},[1872,1875,1878],{"text":1873,"config":1874},"Terms",{"href":1697,"dataGaName":1698,"dataGaLocation":1645},{"text":1876,"config":1877},"Cookies",{"dataGaName":1707,"dataGaLocation":1645,"id":1708,"isOneTrustButton":470},{"text":1879,"config":1880},"Privacy",{"href":1702,"dataGaName":1703,"dataGaLocation":1645},[1882],{"id":1883,"title":7,"body":1177,"config":1884,"content":1886,"description":1177,"extension":1890,"meta":1891,"navigation":470,"path":1892,"seo":1893,"stem":1894,"__hash__":1895},"blogAuthors/en-us/blog/authors/angelo-stavrow.yml",{"template":1885},"BlogAuthor",{"name":7,"config":1887},{"headshot":1888,"ctfId":1889},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Angelo-Stavrow","yml",{},"/en-us/blog/authors/angelo-stavrow",{},"en-us/blog/authors/angelo-stavrow","wOXxlatQaKngJFXsyapIVjnSeuf3D_O-KvmKyA1jGDo",[1897,1906,1914],{"title":1898,"description":1899,"heroImage":1900,"category":1173,"date":1901,"authors":1902,"slug":1905,"externalUrl":1177},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[1903,1904],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1907,"description":1908,"heroImage":1909,"category":1173,"date":1910,"authors":1911,"slug":1913,"externalUrl":1177},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[1912],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1915,"description":1916,"heroImage":1917,"category":1173,"date":1918,"authors":1919,"slug":1921,"externalUrl":1177},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[1920],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1923},[1924,1938,1950,1962],{"id":1925,"categories":1926,"header":1928,"text":1929,"button":1930,"image":1935},"ai-modernization",[1927],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1931,"config":1932},"Get your AI maturity score",{"href":1933,"dataGaName":1934,"dataGaLocation":1411},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1936},{"src":1937},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1939,"categories":1940,"header":1942,"text":1929,"button":1943,"image":1947},"devops-modernization",[1941,1746],"product","Are you just managing tools or shipping innovation?",{"text":1944,"config":1945},"Get your DevOps maturity score",{"href":1946,"dataGaName":1934,"dataGaLocation":1411},"/assessments/devops-modernization-assessment/",{"config":1948},{"src":1949},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1951,"categories":1952,"header":1954,"text":1929,"button":1955,"image":1959},"security-modernization",[1953],"security","Are you trading speed for security?",{"text":1956,"config":1957},"Get your security maturity score",{"href":1958,"dataGaName":1934,"dataGaLocation":1411},"/assessments/security-modernization-assessment/",{"config":1960},{"src":1961},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1963,"paths":1964,"header":1967,"text":1968,"button":1969,"image":1974},"github-azure-migration",[1965,1966],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":1970,"config":1971},"See how GitLab compares to GitHub",{"href":1972,"dataGaName":1973,"dataGaLocation":1411},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1975},{"src":1949},{"header":1977,"blurb":1978,"button":1979,"secondaryButton":1984},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1980,"config":1981},"Get your free trial",{"href":1982,"dataGaName":1213,"dataGaLocation":1983},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1683,"config":1985},{"href":1536,"dataGaName":1218,"dataGaLocation":1983},1786803754610]