[{"data":1,"prerenderedAt":700},["ShallowReactive",2],{"/en-us/blog/moving-to-gitlab-yes-its-worth-it/":3,"navigation-en-us":32,"banner-en-us":449,"footer-en-us":461,"Fabio Akita":672,"next-steps-en-us":685},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":22,"_id":25,"_type":26,"title":27,"_source":28,"_file":29,"_stem":30,"_extension":31},"/en-us/blog/moving-to-gitlab-yes-its-worth-it","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Customer Story: Moving to GitLab! Yes, it's worth it!","Migrating from GitHub to GitLab and setting up your own GitLab instance","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749665885/Blog/Hero%20Images/love-the-sun-gitlab.jpg","https://about.gitlab.com/blog/moving-to-gitlab-yes-its-worth-it","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Customer Story: Moving to GitLab! Yes, it's worth it!\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Fabio Akita\"}],\n        \"datePublished\": \"2016-08-04\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21},[18],"Fabio Akita","2016-08-04","\n\n**Note:** This post is a customer story on the benefits of migrating from GitHub to GitLab, by Fabio Akita, a Brazilian Rubyist.\n{: .note}\n\nI started [evangelizing Git in 2007][evang]. It was a very tough sell to make at the time.\n\nOutside of the kernel development almost no one wanted to learn it and we had very worthy competitors, from Subversion, to Mercurial, to Bazaar, to Darcs, to Perforce, and so on. But those of use that dug deeper knew that Git had the edge and it was a matter of time.\n\nThen GitHub showed up in 2008 and the rest is history. For many years it was just \"cool\" to be in GitHub. The Ruby community drove GitHub up into the sky. Finally it became the status quo and the one real monopoly in information repositories - not just software source code, but everything.\n\nI always knew that we should have a \"local\" option, which is why I tried to [contribute to Gitorious][gitorious] way back in 2009. Other options arose, but eventually GitLab appeared around 2011 and picked up steam in the last couple of years.\n\nGitHub itself raised [USD 350 million in funding][gh-fund] and one of its required goals is to nail the Enterprise Edition for big corporations that don't want their data outside their closed gardens. Although GitHub hosts every single open source project out there, they are themselves closed-source.\n\n[GitLab Inc.][GL] started differently with an open source-first approach with their Community Edition (CE) and having both a GitHub-like hosted option as well as a supported Enterprise Edition for fearsome corporations. They already raised [USD 5.62 million in funding][gl-fund], and they are the most promising alternative to GitHub so far.\n\n\u003C!-- more -->\n\nOf course, there are other platforms such as Atlassian's Bitbucket. But I believe Atlassian's strategy is slower and they have a larger suite of enterprise products to sell first, such as Confluence and Jira. I don't think they ever posed much of a competition against GitHub.\n\nGitLab really started accelerating in 2015 as this [commit graph][comm-graph] shows:\n\n![contributors to gitlabhq](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/contributors-to-gitlabhq.png){: .shadow}\n\nIt's been steadily growing since 2011, but they seem to have crossed the first tipping point around late 2014, from early adopters to the early majority. This became more important as **GitHub** announced their [pricing changes][gh-prices] in May.\n\nThey said they haven't committed to a dead line to enforce the change, so organizations can opt out of the new format for the time being. They are changing from \"limited repositories and unlimited users\" to \"unlimited repositories and limited users\".\n\n## The Cost-Benefit Conundrum\n\nFor example, if you have up to 8 developers in the USD 50/month (20 private repositories), the change won't affect you, as you will pay USD 25/month for 5 users and USD 9 for additional users (total of USD 52/month).\n\nNow, if you have a big team of 100 developers currently in the Diamond Plan of USD 450/month (300 private repositories), you would have to pay USD 25/month + 95 times USD 9, which totals a staggering USD 880/month! **Double the amount!**\n\nThis is an **extra USD 10,560** per year!\n{: .alert .alert-danger}\n\nAnd what does **GitLab** affords you instead?\n\nYou can have way more users and more repositories in a **USD 40/month** virtual box (4GB of RAM, 60GB SSD, 4TB transfer).\n{: .alert .alert-success}\n\nAnd it doesn't stop there. GitLab also has very functional [GitLab Multi Runner][runner] which you can install in a separate box (actually, at least 3 boxes - more on that below).\n\nYou can easily connect this runner to the build system over GitLab so every new git push trigger the runner to run the automated test suite in a Docker image of your choosing. So it's a fully functional, full featured Continuous Integration system nicely integrated in your GitLab project interface:\n\n![pipelines](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/pipelines-cm42-archived-gitlab.png){: .shadow}\n\n![builds](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/test-144-builds-cm42-archived-gitlab.png){: .shadow}\n\nReminds of you anything? Yep, it's a fully functional alternative to Travis-CI, Semaphore, CircleCI or any other CI you're using with a very easy to install procedure. Let's say you're paying **Travis-CI USD 489/month to have 10 concurrent jobs**.\n\nYou can install **GitLab Runner** in **3 boxes of USD 10/month** (1GB RAM, 1 Cores, 30GB SSD) and have way more concurrent jobs (20? 50? Auto-Scale!?) that **runs faster** (in a simple test, one build took 15 minutes over Travis took less than 8 minutes at Digital Ocean).\n\nSo let's make the math for a year's worth of service. First considering no GitHub plan change:\n\nUSD 5,400 (**GitHub**) + USD 5,868 (**Travis**) = **USD 11,268 a year**.\n{: .alert .alert-danger}\n\nNow, the GitLab + GitLab Runner + Digital Ocean for the same features and unlimited users, unlimited repositories, unlimited concurrent builds:\n\nUSD 480 (**GitLab**) + USD 840 (**Runner box**) = **USD 1,320 a year**.\n{: .alert .alert-success}\n\nThis is already **almost 8.5x cheaper** with almost **no change in quality**.\n\nFor the worst case scenario, compare it when **GitHub** decides to enforce the new plans:\n\nUSD 10,560 (**GitHub new plans**) + USD 5,868 (**Travis**) = **USD 16,428**\n{: .alert .alert-danger}\n\nNow the **GitLab** option is **11x cheaper**! You're **saving almost USD 15,000 a year!** This is not something you can ignore in your cost sheet.\n{: .alert .alert-success}\n\nAs I said, the calculations above are only significant in a scenario of a 100 developers. You must do your own math taking into account your team size and number of active projects (you can always archive unused projects).\n\nEven if you don't have 100 developers. Let's consider the scenario for 30 developers in the new GitHub per user plans and a smaller Travis configuration for 5 concurrent jobs:\n\nUSD 3,000 (**GitHub new plan**) + USD 3,000 (**Travis**) = **USD 6,000**\n{: .alert .alert-danger}\n\nIt's **4.5x cheaper** in the **Digital Ocean + GitLab** suite option.\n{: .alert .alert-success}\n\nHeck, let's consider the Current **GitHub** plan (the Platinum one, for up to 125 repositories):\n\nUSD 2,400 (**GitHub current plan**) + USD 3,000 (**Travis**) = **USD 5,400**\n{: .alert .alert-danger}\n\nStill **at least 4x more expensive** than a **GitLab-based** solution!\n{: .alert .alert-success}\n\nAnd how long will it take for a single developer to figure out the set up and migrate everything from GitHub over to the new **GitLab** installation? I will say that you can reserve 1 week of work for the average programmer to do it following the official documentation and my tips and tricks below.\n\n## Installing GitLab CE\n\nI will not bore you with what you can readily find over the Web. I highly recommend you start with the easiest solution first: [Digital Ocean's One-Click Automatic Install][do-inst]. Install it in at least a 4GB RAM machine (you will want to keep it if you like it).\n\nOf course, there is a number of different installation options, from AWS AMI images to Ubuntu packages you can install manually. Study the [documentation].\n\nIt will cost you **USD 40 for a month of trial**. If you want to **save** as much as **tens of thousands of dollar**, this is a bargain.\n\nGitLab has many customization options. You can lock down your private GitLab to allow only users with an official e-mail from your domain, for example. You can configure [OAuth2 providers][omni-auth] so your users can quickly sign in using their GitHub, Facebook, Google or other accounts.\n\n### A Few Gotchas\n\nI've stumbled upon a few caveats in the configuration. Which is why I recommend that you plan ahead - study this entire article ahead of time! -, do a quick install that you can blow away, so you can \"feel\" the environment before trying to migrate all your repos over to your brand new GitLab. As a reference, this is a part of my `/etc/gitlab/gitlab.rb`:\n\n```shell\n# register a domain for your server and place it here:\nexternal_url \"http://my-gitlab-server.com/\"\n\n# you will want to enable [LFS](https://git-lfs.github.com)\ngitlab_rails['lfs_enabled'] = true\n\n# register your emails\ngitlab_rails['gitlab_email_from'] = \"no-reply@my-gitlab-server.com\"\n\n# add your email configuration (template for gmail)\ngitlab_rails['smtp_enable'] = true\ngitlab_rails['smtp_address'] = \"smtp.gmail.com\"\ngitlab_rails['smtp_port'] = 587\ngitlab_rails['smtp_user_name'] = \"-- some no-reply email ---\"\ngitlab_rails['smtp_password'] = \"-- the password ---\"\ngitlab_rails['smtp_domain'] = \"my-gitlab-server.com\"\ngitlab_rails['smtp_authentication'] = \"login\"\ngitlab_rails['smtp_enable_starttls_auto'] = true\ngitlab_rails['smtp_openssl_verify_mode'] = 'peer'\n\n# this is where you enable oauth2 integration\ngitlab_rails['omniauth_enabled'] = true\n\n# CAUTION!\n# This allows users to login without having a user account first. Define the allowed providers\n# using an array, e.g. [\"saml\", \"twitter\"], or as true/false to allow all providers or none.\n# User accounts will be created automatically when authentication was successful.\ngitlab_rails['omniauth_allow_single_sign_on'] = ['github', 'google_oauth2', 'bitbucket']\ngitlab_rails['omniauth_block_auto_created_users'] = true\n\ngitlab_rails['omniauth_providers'] = [\n  {\n    \"name\" => \"github\",\n    \"app_id\" => \"-- github app id --\",\n    \"app_secret\" => \"-- github secret --\",\n    \"url\" => \"https://github.com/\",\n    \"args\" => { \"scope\" => \"user:email\" }\n  },\n  {\n    \"name\" => \"google_oauth2\",\n    \"app_id\" => \"-- google app id --\",\n    \"app_secret\" => \"-- google secret --\",\n    \"args\" => { \"access_type\" => \"offline\", \"approval_prompt\" => '', hd => 'codeminer42.com' }\n  },\n  {\n    \"name\" => \"bitbucket\",\n    \"app_id\" => \"-- bitbucket app id --\",\n    \"app_secret\" => \"-- bitbucket secret id --\",\n    \"url\" => \"https://bitbucket.org/\"\n  }\n]\n\n# if you're importing repos from GitHub, Sidekiq workers can grow as high as 2.5GB of RAM and the default [Sidekiq Killer](https://docs.gitlab.com/ee/operations/sidekiq_memory_killer.html) config will cap it down to 1GB, so you want to either disable it by adding '0' or adding a higher limit\ngitlab_rails['env'] = { 'SIDEKIQ_MEMORY_KILLER_MAX_RSS' => '3000000' }\n```\n\nThere are [dozens of default variables][vars] you can [override], just be careful on your testings.\n\nEvery time you change a configuration, you can just run the following commands:\n\n```shell\nsudo gitlab-ctl reconfigure\nsudo gitlab-ctl restart\n```\n\nYou can open a Rails console to inspect production objects like this:\n\n```shell\ngitlab-rails console\n```\n\nI had a lot of trouble importing big repos from GitHub, but after a few days debugging the problem with GitLab Core Team developers [Douglas Alexandre][douglas], [Gabriel Mazetto][gabriel], a few Merge Requests and some local patching and I was finally able to import relatively big projects (more than 5,000 commits, more than 1,000 issues, more than 1,200 pull requests with several comments worth of discussion threads). A project of this size can take a couple of hours to complete, mainly because it's damn slow to use GitHub's public APIs (they are slow and they have rate limits and abuse detection, so you can't fetch everything as fast as your bandwidth would allow).\n\n(By the way, don't miss GitLab will be over at [Rubyconf Brazil 2016][conf], on Sep 23-24)\n\nMigrating all my GitHub projects took a couple of days, but they all went through smoothly and my team didn't have any trouble, just adjusting their git remote URLs and they're done.\n\nThe import procedure from GitHub is quite complete, it brings not only the git repo per se, but also all the metadata, from labels to comments and pull request history - which is the one that usually takes more time.\n\nBut I'd recommend waiting for at least version 8.11 (it's currently 8.10.3) before trying to import large GitHub projects.\n\nIf you're on Bitbucket, unfortunately there are less features in the importer. It will mostly just bring the source code. So be aware of that if you extensively depend on their pull request system and you want to preserve this history. More feature will come and you can even help them out, they are very resourceful and willing to make GitLab better.\n\n## Side-track: Customizations for every Digital Ocean box\n\nAssume that you should run what's in this section for all new machines you create over Digital Ocean.\n\nFirst of all, they come without a swap file. No matter how much RAM you have, the Linux OS is meant to work better by combining a swap file. You can [read more about it][do-ub] later, for now just run the following as root:\n\n```shell\nfallocate -l 4G /swapfile\nchmod 600 /swapfile\nmkswap /swapfile\nswapon /swapfile\n\nsysctl vm.swappiness=10\nsysctl vm.vfs_cache_pressure=50\n```\n\nEdit the `/etc/fstab` file and add this line:\n\n```shell\n/swapfile   none    swap    sw    0   0\n```\n\nDon't forget to set the [default locale][locale] of your machine. Start by editing the `/etc/environment` file and adding:\n\n```shell\nLC_ALL=en_US.UTF-8\nLANG=en_US.UTF-8\n```\n\nThen run:\n\n```shell\nsudo locale-gen en_US en_US.UTF-8\nsudo dpkg-reconfigure locales\n```\n\nFinally, you should have Ubuntu automatically install stable security patches for you. You don't want to forget machines online without the most current security fixes, so just run this:\n\n```shell\nsudo dpkg-reconfigure --priority=low unattended-upgrades\n```\n\nChoose \"yes\" and you're done. And of course, for every fresh install, it's always good to run the good old:\n\n```shell\nsudo apt-get update && sudo apt-get upgrade\n```\n\nThis is the very basics, I believe it's easier to have an image with all this ready, but if you use the standard Digital Ocean images, these settings should do the trick for now.\n\n## Installing the CI Runner\n\nOnce you finish your GitLab installation, it's [super easy][inst-gl-run] to deploy the GitLab Runner. You can use the same machine but I recommend you install it in a separate machine.\n\nIf you don't know what a runner is, just imagine it like this: It's basically a server connected to the GitLab install. When it's available and online, whenever someone pushes a new commit, merge request, to a repository that has a `gitlab-ci-yml` file present, GitLab will push a command to the runner.\n\nDepending on how you configured the runner, it will receive this command and spawn a new Docker container. Inside the container it will execute whatever you have defined in the `gitlab-ci.yml` file in the project. Usually it's fetching cached files (dependencies, for example), and run your test suite.\n\nIn the most basic setup, you will only have one Runner and any subsequent builds from other users will wait in line until they finish. If you've used external CI services such as Travis-CI or CircleCI, you know that they charge for some number of concurrent builds. And it's **very expensive**.\n\nThe less concurrent builds available, the more your users will have to wait for feedback on their changes, and less productive you will become. People may even start to avoid adding new tests, or completely ignore the tests, which will really hurt the quality of your project over time. If there is one thing you **must not** do is not having good automated test suites.\n\nGabriel Mazetto pointed me to a very important GitLab CI Runner feature: [auto-scaling]. This is what they use in their hosted offering over at [GitLab.com].\n\nYou can easily set up a runner that can use \"docker-machine\" and your IaaS provider APIs to spin up machines on the fly to run as many concurrent builds as you want, and it will be super cheap!\n\nFor example, on Digital Ocean you can be charged USD 0.06 (6 cents) per hour of usage of a 4GB machine. Over at AWS EC2 you can be charged USD 0.041 per hour for an m3.medium machine.\n\nThere is extensive documentation but I will try to summarize what you have to do. For more details I highly recommend you to study their [official documentation][doc-runner].\n\nStart by creating 3 new machines at Digital Ocean, all in the same Region with private networking enabled! I will list a fake private IP address just for the sake of advancing in the configuration examples:\n\n- a 1GB machine called \"docker-registry-mirror\", (ex 10.0.0.1)\n- a 1GB machine called \"ci-cache\", (ex 10.0.0.2)\n- a 1GB machine called \"ci-runner\", (ex 10.0.0.3)\n\nYeah, they can be small as very little will run on them. You can be conservative and choose the 2GB RAM options just to be on the safe side (and pricing will still be super cheap).\n\nDon't forget to execute the basic configuration I mentioned above to enable a swapfile, auto security update and locale regeneration.\n\nSSH in to \"docker-registry-mirror\" and just run:\n\n```shell\ndocker run -d -p 6000:5000 \\\n    -e REGISTRY_PROXY_REMOTEURL=https://registry-1.docker.io \\\n    --restart always \\\n    --name registry registry:2\n```\n\nNow you will have a local Docker images registry proxy and cache at 10.0.0.1:6000 (take note of the real private IP).\n\nSSH in to \"ci-cache\" and run:\n\n```shell\nmkdir -p /export/runner\n\ndocker run -it --restart always -p 9005:9000 \\\n        -v /.minio:/root/.minio -v /export:/export \\\n        --name minio \\\n        minio/minio:latest /export\n```\n\nNow you will have an AWS S3 clone called [Minio] running. I didn't know this project even existed, but it is a nifty little service written in Go to clone the AWS S3 behavior and APIs. So now you can have your very own S3 inside your infrastructure!\n\nAfter Docker spin ups, it will print out the Access Key and Secret keys, make notes. And this service will be running at `10.0.0.2:9005`.\n\nYou can even open a browser and see their web interface at `http://10.0.0.2:9005` and use the access and secret keys to login. Make sure you have a bucket named \"runner\". The files will be stored at the `/export/runner` directory.\n\n![Minio browser](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/minio-browser.png){: .shadow}\n\nMake sure the [bucket name is valid][bucket] (it must be a valid DNS naming, for example, **DO NOT use underlines**).\n\nOpen this URL from your freshly installed **GitLab CE**: `http://yourgitlab.com/admin/runners` and take note of the **Registration Token**. Let's say it's `1aaaa_Z1AbB2CdefGhij`\n\n![admin area](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/admin-area-gitlab.png){: .shadow}\n\nFinally, SSH in to \"ci-runner\" and run:\n\n```shell\ncurl -L https://github.com/docker/machine/releases/download/v0.7.0/docker-machine-`uname -s`-`uname -m` > /usr/local/bin/docker-machine\n\nchmod +x /usr/local/bin/docker-machine\n\ncurl -L https://packages.gitlab.com/install/repositories/runner/gitlab-ci-multi-runner/script.deb.sh | sudo bash\n\nsudo apt-get install gitlab-ci-multi-runner\n\nrm -Rf ~/.docker # just to make sure\n```\n\nNow you can register this new runner with your GitLab install, you will need the Registration Token mentioned above.\n\n```shell\nsudo gitlab-ci-multi-runner register\n```\n\nYou will be asked a few questions, and this is what you can answer:\n\n```shell\nPlease enter the gitlab-ci coordinator URL (e.g. https://gitlab.com/ci )\nhttps://yourgitlab.com/ci\nPlease enter the gitlab-ci token for this runner\n1aaaa_Z1AbB2CdefGhij # as in the example above\nPlease enter the gitlab-ci description for this runner\nmy-autoscale-runner\nINFO[0034] fcf5c619 Registering runner... succeeded\nPlease enter the executor: shell, docker, docker-ssh, docker+machine, docker-ssh+machine, ssh?\ndocker+machine\nPlease enter the Docker image (eg. ruby:2.1):\ncodeminer42/ci-ruby:2.3\nINFO[0037] Runner registered successfully. Feel free to start it, but if it's\nrunning already the config should be automatically reloaded!\n```\n\nLet's make a copy of the original configuration, just to be safe:\n\n```shell\ncp /etc/gitlab-runner/config.toml /etc/gitlab-runner/config.bak\n```\n\nCopy the first few lines of this file (you want the token), it will look like this:\n\n```shell\nconcurrent = 1\ncheck_interval = 0\n\n[[runners]]\n  name = \"my-autoscale-runner\"\n  url = \"http://yourgitlab.com/ci\"\n  token = \"--- generated runner token ---\"\n  executor = \"docker+machine\"\n```\n\nThe important part here is the \"token\". You will want to take note of it. And now you also will want to create a [new API Token over at Digital Ocean][do-tok]. Just Generate a New Token and take note.\n\nYou can now replace the entire `config.toml` file for this:\n\n```toml\nconcurrent = 20\ncheck_interval = 0\n\n[[runners]]\n  name = \"my-autoscale-runner\"\n  url = \"http://yourgitlab.com/ci\"\n  token = \"--- generated runner token ---\"\n  executor = \"docker+machine\"\n  limit = 15\n  [runners.docker]\n    tls_verify = false\n    image = \"codeminer42/ci-ruby:2.3\"\n    privileged = false\n  [runners.machine]\n    IdleCount = 2                   # There must be 2 machines in Idle state\n    IdleTime = 1800                 # Each machine can be in Idle state up to 30 minutes (after this it will be removed)\n    MaxBuilds = 100                 # Each machine can handle up to 100 builds in a row (after this it will be removed)\n    MachineName = \"ci-auto-scale-%s\"   # Each machine will have a unique name ('%s' is required)\n    MachineDriver = \"digitalocean\"  # Docker Machine is using the 'digitalocean' driver\n    MachineOptions = [\n        \"digitalocean-image=coreos-beta\",\n        \"digitalocean-ssh-user=core\",\n        \"digitalocean-access-token=-- your new Digital Ocean API Token --\",\n        \"digitalocean-region=nyc1\",\n        \"digitalocean-size=4gb\",\n        \"digitalocean-private-networking\",\n        \"engine-registry-mirror=http://10.0.0.1:6000\"\n    ]\n  [runners.cache]\n    Type = \"s3\"   # The Runner is using a distributed cache with Amazon S3 service\n    ServerAddress = \"10.0.0.2:9005\"  # minio\n    AccessKey = \"-- your minio access key --\"\n    SecretKey = \"-- your minio secret key\"\n    BucketName = \"runner\"\n    Insecure = true # Use Insecure only when using with Minio, without the TLS certificate enabled\n```\n\nAnd you can restart the runner to pick up the new configuration like this:\n\n```shell\ngitlab-ci-multi-runner restart\n```\n\nAs I said before, you will want to read the extensive [official documentation][auto-sc-doc] (and every link within).\n\nIf you did everything right, changing the correct private IPs for the docker registry and cache, the correct tokens, and so forth, you can log in to your Digital Ocean dashboard and you will see something like this:\n\n![DO droplets](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/digital-ocean-droplets.png){: .shadow}\n\nAnd from the `ci-runner` machine, you can list them like this:\n\n```shell\n# docker-machine ls\n\nNAME                                    ACTIVE        DRIVER   STATE     URL            SWARM   DOCKER    ERRORS\nrunner-xxxx-ci-auto-scale-xxxx-xxxx  -  digitalocean  Running  tcp://191.168.0.1:237    v1.10.3\nrunner-xxxx-ci-auto-scale-xxxx-xxxx  -  digitalocean  Running  tcp://192.169.0.2:2376   v1.10.3\n```\n\nThey should not list any errors, meaning that they are up and running, waiting for new builds to start.\n\nThere will be 2 new machines listed in your Digital Ocean dashboard, named \"runner-xxxxx-ci-auto-scale-xxxxx\". This is what `IdleCount = 2` does. If they stay idle for more than 30 minutes (`IdleTime = 1800`) they will be shut down so you don't get charged.\n\nYou can have several \"runner\" definitions, each with a `limit` of builds/machines that can be spawned in Digital Ocean. You can have other runner definitions for other providers, for example. But in this example we are limited to at most 15 machines, so 15 concurrent builds.\n\nThe `concurrent` limit is a global setting. So if I had 3 runner definitions, each with a `limit` of 15, they would still be globally limited to 20 as defined in the `concurrent` global variable.\n\nYou can use different providers for specific needs, for example, to run macOS builds or Raspberry Pi builds or other exotic kinds of builds. In the example I am keeping it simple and just setting many builds in the same provider (Digital Ocean).\n\nAnd don't worry about the monthly fee for each machine. When used in this manner, you will be paying per hour.\n\nAlso, make sure you spun up all your machines (docker-registry, minio cache, CI runner) all with private networking enabled (so they talk through the internal VLAN instead of having to go all the way through the public internet) and that they are all in the same region data center (NYC1 is New York 1 - New York has 3 sub-regions, for example). Don't start machines in different regions.\n\nBecause we have Docker proxy/cache and Minio/S3 cache, your builds will take longer the first time (let's say, 5 minutes), and then subsequent build will fetch everything from the cache (taking, let's say, 1:30 minute). It's fast and it's convenient.\n\n## Setting up each Project for the Runner\n\nThe Runner is one of the newest pieces of the GitLab ecosystem so you might have some trouble at first to figure out a decent configuration. But once you have the whole infrastructure figured out as described in the previous section, now it's as easy as adding a `.gitlab-ci.yml` file to your root directory. Something like this:\n\n```yaml\n# This file is a template, and might need editing before it works on your project.\nimage: codeminer42/ci-ruby:2.3\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: https://docs.gitlab.com/ee/ci/docker/using_docker_images.html#what-is-service\nservices:\n  - postgres:latest\n  - redis:latest\n\ncache:\n  key: your-project-name\n  untracked: true\n  paths:\n    - .ci_cache/\n\nvariables:\n  RAILS_ENV: 'test'\n  DATABASE_URL: postgresql://postgres:@postgres\n  CODECLIMATE_REPO_TOKEN: -- your codeclimate project token --\n\nbefore_script:\n  - bundle install --without development production -j $(nproc) --path .ci_cache\n  - cp .env.sample .env\n  - cp config/database.yml.example config/database.yml\n  - bundle exec rake db:create db:migrate\n\ntest:\n  script:\n    - xvfb-run bundle exec rspec\n```\n\nMy team at [Codeminer 42][codeminer] prepared a simple [Docker image] with useful stuff pre-installed (such as the newest phantomjs, xvfb, etc), so it's now super easy to enable automated builds within GitLab by just adding this file to the repositories. (Thanks to Carlos Lopes, Danilo Resende and Paulo Diovanni - who will be talking about [Docker at Rubyconf Brasil 2016][docker-conf], by the way).\n\nGitLab CI even supports building a pending Merge Request, and you can enforce the request so it can only be merged if builds pass, just like in GitHub + Travis. And as Code Climate is agnostic to Repository host or CI runner, you can easily integrate it as well.\n\n![merge requests](https://about.gitlab.com/images/blogimages/moving-to-gitlab-yes-its-worth-it/settings-codeminer42-cm-fulcrum-gitlab.png){: .shadow}\n\n## Conclusion\n\nThe math is hard to argue against: the GitLab + GitLab CI + Digital Ocean combo is a big win. GitLab's interface is very familiar so users from GitHub or Bitbucket will feel quite at home in no time.\n\nWe can use all the [Git flows] we're used to.\n\n**GitLab CE** is still a work in progress though, the team is increasing their pace but there are currently more than [4,200 open issues][gl-issues]. But as this is all Ruby on Rails and Ruby tooling, you can easily jump in and contribute. No contribution is too small. Just by reporting how to reproduce a bug is help enough to assist the developers to figure out how to improve faster.\n\nBut don't shy away because of the open issues, it's fully functional as of right now and I have not found any bugs that could be considered show stoppers.\n\nThey have many things right. First of all, it's a \"simple\" Ruby on Rails project. It's a no-thrills front-end with plain JQuery. The choice of HAML for the views is questionable but it doesn't hurt. They use good old Sidekiq+Redis for asynchronous jobs. No black magic here. A pure monolith that's not difficult to understand and to contribute.\n\nThe APIs are all written using Grape. They have the [GitLab CE][ce] project separated from other components, such as the [GitLab Shell][shell] and [GitLab CI Multi-Runner][run].\n\nThey also forked [Omnibus][omn] in order to be able to package the CE Rails project as a \".deb\". Everything is orchestrated with Docker. And when a new version is available, you only need to `apt-get update && apt-get upgrade` and it will do all the work of backing up and migrating Postgresql, updating the code, bundling in new dependencies, restarting the services and so forth. It's super convenient and you should take a look at this project if you have complicated Rails deployments into your own infrastructure (out of Heroku, for example).\n\nI am almost done moving hundreds of repositories from both Bitbucket and GitHub to GitLab right now and the developers from my company are already using it in a daily basis without any problems. We are almost at the point where we can disengage from Bitbucket, GitHub and external CIs.\n\nYou will be surprised how **easy your company can do it** too and **save a couple thousand dollars** in the process, while **having fun doing it**!\n\n----\n\n_**Note:** this article was originally posted by [AkitaOnRails]._\n\n\u003C!-- identifiers -->\n\n[AkitaOnRails]: http://www.akitaonrails.com/2016/08/03/moving-to-gitlab-yes-it-s-worth-it\n[auto-sc-doc]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/configuration/autoscale.md\n[auto-scaling]: /releases/2016/03/29/gitlab-runner-1-1-released/\n[bucket]: http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html\n[ce]: https://gitlab.com/gitlab-org/gitlab-ce\n[codeminer]: http://www.codeminer42.com/\n[comm-graph]: https://github.com/gitlabhq/gitlabhq/graphs/contributors?from=2015-03-14&to=2016-08-02&type=c\n[conf]: http://www.rubyconf.com.br/pt-BR/speakers#Gabriel%20Gon%C3%A7alves%20Nunes%20Mazetto\n[do-inst]: https://www.digitalocean.com/features/one-click-apps/gitlab/\n[do-tok]: https://www.digitalocean.com/community/tutorials/how-to-use-the-digitalocean-api-v2\n[do-ub]: https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04\n[doc-runner]: https://gitlab.com/gitlab-org/gitlab-runner/blob/master/docs/install/autoscaling.md#prepare-the-docker-registry-and-cache-server\n[Docker image]: https://hub.docker.com/r/codeminer42/ci-ruby/\n[docker-conf]: http://www.rubyconf.com.br/pt-BR/speakers#Paulo%20Diovani%20Gon%C3%A7alves\n[documentation]: /install/\n[douglas]: https://gitlab.com/dbalexandre\n[evang]: http://www.akitaonrails.com/2007/9/22/jogar-pedra-em-gato-morto-por-que-subversion-no-presta\n[gabriel]: https://gitlab.com/brodock\n[gh-fund]: https://www.crunchbase.com/organization/github#/entity\n[gh-prices]: https://github.com/blog/2164-introducing-unlimited-private-repositories\n[Git flows]: /2014/09/29/gitlab-flow/\n[GitLab.com]: https://gitlab.com/users/sign_in\n[gitorious]: https://gitorious.org/gitorious/oboxodo-gitorious?p=gitorious:oboxodo-gitorious.git;a=search;h=9f6bdf5887c65a440bc3fdc43a14652f42ddf103;s=Fabio+Akita;st=committer\n[gl-fund]: https://www.crunchbase.com/organization/gitlab-com#/entity\n[gl-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues\n[gl]: /\n[inst-gl-run]: /blog/how-to-set-up-gitlab-runner-on-digitalocean/\n[locale]: http://askubuntu.com/questions/162391/how-do-i-fix-my-locale-issue\n[Minio]: https://github.com/minio/minio\n[omn]: https://gitlab.com/gitlab-org/omnibus-gitlab\n[omni-auth]: https://docs.gitlab.com/ee/integration/omniauth.html\n[override]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/environment-variables.md\n[run]: https://gitlab.com/gitlab-org/gitlab-runner\n[runner]: https://gitlab.com/gitlab-org/gitlab-runner\n[shell]: https://gitlab.com/gitlab-org/gitlab-shell\n[vars]: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb#L57\n","open-source",{"slug":23,"featured":6,"template":24},"moving-to-gitlab-yes-its-worth-it","BlogPost","content:en-us:blog:moving-to-gitlab-yes-its-worth-it.yml","yaml","Moving To Gitlab Yes Its Worth It","content","en-us/blog/moving-to-gitlab-yes-its-worth-it.yml","en-us/blog/moving-to-gitlab-yes-its-worth-it","yml",{"_path":33,"_dir":34,"_draft":6,"_partial":6,"_locale":7,"data":35,"_id":445,"_type":26,"title":446,"_source":28,"_file":447,"_stem":448,"_extension":31},"/shared/en-us/main-navigation","en-us",{"logo":36,"freeTrial":41,"sales":46,"login":51,"items":56,"search":386,"minimal":417,"duo":436},{"config":37},{"href":38,"dataGaName":39,"dataGaLocation":40},"/","gitlab logo","header",{"text":42,"config":43},"Get free trial",{"href":44,"dataGaName":45,"dataGaLocation":40},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":47,"config":48},"Talk to sales",{"href":49,"dataGaName":50,"dataGaLocation":40},"/sales/","sales",{"text":52,"config":53},"Sign in",{"href":54,"dataGaName":55,"dataGaLocation":40},"https://gitlab.com/users/sign_in/","sign in",[57,101,197,202,307,367],{"text":58,"config":59,"cards":61,"footer":84},"Platform",{"dataNavLevelOne":60},"platform",[62,68,76],{"title":58,"description":63,"link":64},"The most comprehensive AI-powered DevSecOps Platform",{"text":65,"config":66},"Explore our Platform",{"href":67,"dataGaName":60,"dataGaLocation":40},"/platform/",{"title":69,"description":70,"link":71},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":72,"config":73},"Meet GitLab Duo",{"href":74,"dataGaName":75,"dataGaLocation":40},"/gitlab-duo/","gitlab duo ai",{"title":77,"description":78,"link":79},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":80,"config":81},"Learn more",{"href":82,"dataGaName":83,"dataGaLocation":40},"/why-gitlab/","why gitlab",{"title":85,"items":86},"Get started with",[87,92,97],{"text":88,"config":89},"Platform Engineering",{"href":90,"dataGaName":91,"dataGaLocation":40},"/solutions/platform-engineering/","platform engineering",{"text":93,"config":94},"Developer Experience",{"href":95,"dataGaName":96,"dataGaLocation":40},"/developer-experience/","Developer experience",{"text":98,"config":99},"MLOps",{"href":100,"dataGaName":98,"dataGaLocation":40},"/topics/devops/the-role-of-ai-in-devops/",{"text":102,"left":103,"config":104,"link":106,"lists":110,"footer":179},"Product",true,{"dataNavLevelOne":105},"solutions",{"text":107,"config":108},"View all Solutions",{"href":109,"dataGaName":105,"dataGaLocation":40},"/solutions/",[111,136,158],{"title":112,"description":113,"link":114,"items":119},"Automation","CI/CD and automation to accelerate deployment",{"config":115},{"icon":116,"href":117,"dataGaName":118,"dataGaLocation":40},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[120,124,128,132],{"text":121,"config":122},"CI/CD",{"href":123,"dataGaLocation":40,"dataGaName":121},"/solutions/continuous-integration/",{"text":125,"config":126},"AI-Assisted Development",{"href":74,"dataGaLocation":40,"dataGaName":127},"AI assisted development",{"text":129,"config":130},"Source Code Management",{"href":131,"dataGaLocation":40,"dataGaName":129},"/solutions/source-code-management/",{"text":133,"config":134},"Automated Software Delivery",{"href":117,"dataGaLocation":40,"dataGaName":135},"Automated software delivery",{"title":137,"description":138,"link":139,"items":144},"Security","Deliver code faster without compromising security",{"config":140},{"href":141,"dataGaName":142,"dataGaLocation":40,"icon":143},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[145,148,153],{"text":146,"config":147},"Security & Compliance",{"href":141,"dataGaLocation":40,"dataGaName":146},{"text":149,"config":150},"Software Supply Chain Security",{"href":151,"dataGaLocation":40,"dataGaName":152},"/solutions/supply-chain/","Software supply chain security",{"text":154,"config":155},"Compliance & Governance",{"href":156,"dataGaLocation":40,"dataGaName":157},"/solutions/continuous-software-compliance/","Compliance and governance",{"title":159,"link":160,"items":165},"Measurement",{"config":161},{"icon":162,"href":163,"dataGaName":164,"dataGaLocation":40},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[166,170,174],{"text":167,"config":168},"Visibility & Measurement",{"href":163,"dataGaLocation":40,"dataGaName":169},"Visibility and Measurement",{"text":171,"config":172},"Value Stream Management",{"href":173,"dataGaLocation":40,"dataGaName":171},"/solutions/value-stream-management/",{"text":175,"config":176},"Analytics & Insights",{"href":177,"dataGaLocation":40,"dataGaName":178},"/solutions/analytics-and-insights/","Analytics and insights",{"title":180,"items":181},"GitLab for",[182,187,192],{"text":183,"config":184},"Enterprise",{"href":185,"dataGaLocation":40,"dataGaName":186},"/enterprise/","enterprise",{"text":188,"config":189},"Small Business",{"href":190,"dataGaLocation":40,"dataGaName":191},"/small-business/","small business",{"text":193,"config":194},"Public Sector",{"href":195,"dataGaLocation":40,"dataGaName":196},"/solutions/public-sector/","public sector",{"text":198,"config":199},"Pricing",{"href":200,"dataGaName":201,"dataGaLocation":40,"dataNavLevelOne":201},"/pricing/","pricing",{"text":203,"config":204,"link":206,"lists":210,"feature":294},"Resources",{"dataNavLevelOne":205},"resources",{"text":207,"config":208},"View all resources",{"href":209,"dataGaName":205,"dataGaLocation":40},"/resources/",[211,244,266],{"title":212,"items":213},"Getting started",[214,219,224,229,234,239],{"text":215,"config":216},"Install",{"href":217,"dataGaName":218,"dataGaLocation":40},"/install/","install",{"text":220,"config":221},"Quick start guides",{"href":222,"dataGaName":223,"dataGaLocation":40},"/get-started/","quick setup checklists",{"text":225,"config":226},"Learn",{"href":227,"dataGaLocation":40,"dataGaName":228},"https://university.gitlab.com/","learn",{"text":230,"config":231},"Product documentation",{"href":232,"dataGaName":233,"dataGaLocation":40},"https://docs.gitlab.com/","product documentation",{"text":235,"config":236},"Best practice videos",{"href":237,"dataGaName":238,"dataGaLocation":40},"/getting-started-videos/","best practice videos",{"text":240,"config":241},"Integrations",{"href":242,"dataGaName":243,"dataGaLocation":40},"/integrations/","integrations",{"title":245,"items":246},"Discover",[247,252,256,261],{"text":248,"config":249},"Customer success stories",{"href":250,"dataGaName":251,"dataGaLocation":40},"/customers/","customer success stories",{"text":253,"config":254},"Blog",{"href":255,"dataGaName":5,"dataGaLocation":40},"/blog/",{"text":257,"config":258},"Remote",{"href":259,"dataGaName":260,"dataGaLocation":40},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":262,"config":263},"TeamOps",{"href":264,"dataGaName":265,"dataGaLocation":40},"/teamops/","teamops",{"title":267,"items":268},"Connect",[269,274,279,284,289],{"text":270,"config":271},"GitLab Services",{"href":272,"dataGaName":273,"dataGaLocation":40},"/services/","services",{"text":275,"config":276},"Community",{"href":277,"dataGaName":278,"dataGaLocation":40},"/community/","community",{"text":280,"config":281},"Forum",{"href":282,"dataGaName":283,"dataGaLocation":40},"https://forum.gitlab.com/","forum",{"text":285,"config":286},"Events",{"href":287,"dataGaName":288,"dataGaLocation":40},"/events/","events",{"text":290,"config":291},"Partners",{"href":292,"dataGaName":293,"dataGaLocation":40},"/partners/","partners",{"backgroundColor":295,"textColor":296,"text":297,"image":298,"link":302},"#2f2a6b","#fff","Insights for the future of software development",{"altText":299,"config":300},"the source promo card",{"src":301},"/images/navigation/the-source-promo-card.svg",{"text":303,"config":304},"Read the latest",{"href":305,"dataGaName":306,"dataGaLocation":40},"/the-source/","the source",{"text":308,"config":309,"lists":311},"Company",{"dataNavLevelOne":310},"company",[312],{"items":313},[314,319,325,327,332,337,342,347,352,357,362],{"text":315,"config":316},"About",{"href":317,"dataGaName":318,"dataGaLocation":40},"/company/","about",{"text":320,"config":321,"footerGa":324},"Jobs",{"href":322,"dataGaName":323,"dataGaLocation":40},"/jobs/","jobs",{"dataGaName":323},{"text":285,"config":326},{"href":287,"dataGaName":288,"dataGaLocation":40},{"text":328,"config":329},"Leadership",{"href":330,"dataGaName":331,"dataGaLocation":40},"/company/team/e-group/","leadership",{"text":333,"config":334},"Team",{"href":335,"dataGaName":336,"dataGaLocation":40},"/company/team/","team",{"text":338,"config":339},"Handbook",{"href":340,"dataGaName":341,"dataGaLocation":40},"https://handbook.gitlab.com/","handbook",{"text":343,"config":344},"Investor relations",{"href":345,"dataGaName":346,"dataGaLocation":40},"https://ir.gitlab.com/","investor relations",{"text":348,"config":349},"Trust Center",{"href":350,"dataGaName":351,"dataGaLocation":40},"/security/","trust center",{"text":353,"config":354},"AI Transparency Center",{"href":355,"dataGaName":356,"dataGaLocation":40},"/ai-transparency-center/","ai transparency center",{"text":358,"config":359},"Newsletter",{"href":360,"dataGaName":361,"dataGaLocation":40},"/company/contact/","newsletter",{"text":363,"config":364},"Press",{"href":365,"dataGaName":366,"dataGaLocation":40},"/press/","press",{"text":368,"config":369,"lists":370},"Contact us",{"dataNavLevelOne":310},[371],{"items":372},[373,376,381],{"text":47,"config":374},{"href":49,"dataGaName":375,"dataGaLocation":40},"talk to sales",{"text":377,"config":378},"Get help",{"href":379,"dataGaName":380,"dataGaLocation":40},"/support/","get help",{"text":382,"config":383},"Customer portal",{"href":384,"dataGaName":385,"dataGaLocation":40},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":387,"login":388,"suggestions":395},"Close",{"text":389,"link":390},"To search repositories and projects, login to",{"text":391,"config":392},"gitlab.com",{"href":54,"dataGaName":393,"dataGaLocation":394},"search login","search",{"text":396,"default":397},"Suggestions",[398,400,404,406,410,414],{"text":69,"config":399},{"href":74,"dataGaName":69,"dataGaLocation":394},{"text":401,"config":402},"Code Suggestions (AI)",{"href":403,"dataGaName":401,"dataGaLocation":394},"/solutions/code-suggestions/",{"text":121,"config":405},{"href":123,"dataGaName":121,"dataGaLocation":394},{"text":407,"config":408},"GitLab on AWS",{"href":409,"dataGaName":407,"dataGaLocation":394},"/partners/technology-partners/aws/",{"text":411,"config":412},"GitLab on Google Cloud",{"href":413,"dataGaName":411,"dataGaLocation":394},"/partners/technology-partners/google-cloud-platform/",{"text":415,"config":416},"Why GitLab?",{"href":82,"dataGaName":415,"dataGaLocation":394},{"freeTrial":418,"mobileIcon":423,"desktopIcon":428,"secondaryButton":431},{"text":419,"config":420},"Start free trial",{"href":421,"dataGaName":45,"dataGaLocation":422},"https://gitlab.com/-/trials/new/","nav",{"altText":424,"config":425},"Gitlab Icon",{"src":426,"dataGaName":427,"dataGaLocation":422},"/images/brand/gitlab-logo-tanuki.svg","gitlab icon",{"altText":424,"config":429},{"src":430,"dataGaName":427,"dataGaLocation":422},"/images/brand/gitlab-logo-type.svg",{"text":432,"config":433},"Get Started",{"href":434,"dataGaName":435,"dataGaLocation":422},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":437,"mobileIcon":441,"desktopIcon":443},{"text":438,"config":439},"Learn more about GitLab Duo",{"href":74,"dataGaName":440,"dataGaLocation":422},"gitlab duo",{"altText":424,"config":442},{"src":426,"dataGaName":427,"dataGaLocation":422},{"altText":424,"config":444},{"src":430,"dataGaName":427,"dataGaLocation":422},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":450,"_dir":34,"_draft":6,"_partial":6,"_locale":7,"title":451,"button":452,"config":456,"_id":458,"_type":26,"_source":28,"_file":459,"_stem":460,"_extension":31},"/shared/en-us/banner","GitLab Duo Agent Platform is now in public beta!",{"text":80,"config":453},{"href":454,"dataGaName":455,"dataGaLocation":40},"/gitlab-duo/agent-platform/","duo banner",{"layout":457},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":462,"_dir":34,"_draft":6,"_partial":6,"_locale":7,"data":463,"_id":668,"_type":26,"title":669,"_source":28,"_file":670,"_stem":671,"_extension":31},"/shared/en-us/main-footer",{"text":464,"source":465,"edit":471,"contribute":476,"config":481,"items":486,"minimal":660},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":466,"config":467},"View page source",{"href":468,"dataGaName":469,"dataGaLocation":470},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":472,"config":473},"Edit this page",{"href":474,"dataGaName":475,"dataGaLocation":470},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":477,"config":478},"Please contribute",{"href":479,"dataGaName":480,"dataGaLocation":470},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":482,"facebook":483,"youtube":484,"linkedin":485},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[487,510,567,596,630],{"title":58,"links":488,"subMenu":493},[489],{"text":490,"config":491},"DevSecOps platform",{"href":67,"dataGaName":492,"dataGaLocation":470},"devsecops platform",[494],{"title":198,"links":495},[496,500,505],{"text":497,"config":498},"View plans",{"href":200,"dataGaName":499,"dataGaLocation":470},"view plans",{"text":501,"config":502},"Why Premium?",{"href":503,"dataGaName":504,"dataGaLocation":470},"/pricing/premium/","why premium",{"text":506,"config":507},"Why Ultimate?",{"href":508,"dataGaName":509,"dataGaLocation":470},"/pricing/ultimate/","why ultimate",{"title":511,"links":512},"Solutions",[513,518,521,523,528,533,537,540,544,549,551,554,557,562],{"text":514,"config":515},"Digital transformation",{"href":516,"dataGaName":517,"dataGaLocation":470},"/topics/digital-transformation/","digital transformation",{"text":146,"config":519},{"href":141,"dataGaName":520,"dataGaLocation":470},"security & compliance",{"text":135,"config":522},{"href":117,"dataGaName":118,"dataGaLocation":470},{"text":524,"config":525},"Agile development",{"href":526,"dataGaName":527,"dataGaLocation":470},"/solutions/agile-delivery/","agile delivery",{"text":529,"config":530},"Cloud transformation",{"href":531,"dataGaName":532,"dataGaLocation":470},"/topics/cloud-native/","cloud transformation",{"text":534,"config":535},"SCM",{"href":131,"dataGaName":536,"dataGaLocation":470},"source code management",{"text":121,"config":538},{"href":123,"dataGaName":539,"dataGaLocation":470},"continuous integration & delivery",{"text":541,"config":542},"Value stream management",{"href":173,"dataGaName":543,"dataGaLocation":470},"value stream management",{"text":545,"config":546},"GitOps",{"href":547,"dataGaName":548,"dataGaLocation":470},"/solutions/gitops/","gitops",{"text":183,"config":550},{"href":185,"dataGaName":186,"dataGaLocation":470},{"text":552,"config":553},"Small business",{"href":190,"dataGaName":191,"dataGaLocation":470},{"text":555,"config":556},"Public sector",{"href":195,"dataGaName":196,"dataGaLocation":470},{"text":558,"config":559},"Education",{"href":560,"dataGaName":561,"dataGaLocation":470},"/solutions/education/","education",{"text":563,"config":564},"Financial services",{"href":565,"dataGaName":566,"dataGaLocation":470},"/solutions/finance/","financial services",{"title":203,"links":568},[569,571,573,575,578,580,582,584,586,588,590,592,594],{"text":215,"config":570},{"href":217,"dataGaName":218,"dataGaLocation":470},{"text":220,"config":572},{"href":222,"dataGaName":223,"dataGaLocation":470},{"text":225,"config":574},{"href":227,"dataGaName":228,"dataGaLocation":470},{"text":230,"config":576},{"href":232,"dataGaName":577,"dataGaLocation":470},"docs",{"text":253,"config":579},{"href":255,"dataGaName":5,"dataGaLocation":470},{"text":248,"config":581},{"href":250,"dataGaName":251,"dataGaLocation":470},{"text":257,"config":583},{"href":259,"dataGaName":260,"dataGaLocation":470},{"text":270,"config":585},{"href":272,"dataGaName":273,"dataGaLocation":470},{"text":262,"config":587},{"href":264,"dataGaName":265,"dataGaLocation":470},{"text":275,"config":589},{"href":277,"dataGaName":278,"dataGaLocation":470},{"text":280,"config":591},{"href":282,"dataGaName":283,"dataGaLocation":470},{"text":285,"config":593},{"href":287,"dataGaName":288,"dataGaLocation":470},{"text":290,"config":595},{"href":292,"dataGaName":293,"dataGaLocation":470},{"title":308,"links":597},[598,600,602,604,606,608,610,614,619,621,623,625],{"text":315,"config":599},{"href":317,"dataGaName":310,"dataGaLocation":470},{"text":320,"config":601},{"href":322,"dataGaName":323,"dataGaLocation":470},{"text":328,"config":603},{"href":330,"dataGaName":331,"dataGaLocation":470},{"text":333,"config":605},{"href":335,"dataGaName":336,"dataGaLocation":470},{"text":338,"config":607},{"href":340,"dataGaName":341,"dataGaLocation":470},{"text":343,"config":609},{"href":345,"dataGaName":346,"dataGaLocation":470},{"text":611,"config":612},"Sustainability",{"href":613,"dataGaName":611,"dataGaLocation":470},"/sustainability/",{"text":615,"config":616},"Diversity, inclusion and belonging (DIB)",{"href":617,"dataGaName":618,"dataGaLocation":470},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":348,"config":620},{"href":350,"dataGaName":351,"dataGaLocation":470},{"text":358,"config":622},{"href":360,"dataGaName":361,"dataGaLocation":470},{"text":363,"config":624},{"href":365,"dataGaName":366,"dataGaLocation":470},{"text":626,"config":627},"Modern Slavery Transparency Statement",{"href":628,"dataGaName":629,"dataGaLocation":470},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":631,"links":632},"Contact Us",[633,636,638,640,645,650,655],{"text":634,"config":635},"Contact an expert",{"href":49,"dataGaName":50,"dataGaLocation":470},{"text":377,"config":637},{"href":379,"dataGaName":380,"dataGaLocation":470},{"text":382,"config":639},{"href":384,"dataGaName":385,"dataGaLocation":470},{"text":641,"config":642},"Status",{"href":643,"dataGaName":644,"dataGaLocation":470},"https://status.gitlab.com/","status",{"text":646,"config":647},"Terms of use",{"href":648,"dataGaName":649,"dataGaLocation":470},"/terms/","terms of use",{"text":651,"config":652},"Privacy statement",{"href":653,"dataGaName":654,"dataGaLocation":470},"/privacy/","privacy statement",{"text":656,"config":657},"Cookie preferences",{"dataGaName":658,"dataGaLocation":470,"id":659,"isOneTrustButton":103},"cookie preferences","ot-sdk-btn",{"items":661},[662,664,666],{"text":646,"config":663},{"href":648,"dataGaName":649,"dataGaLocation":470},{"text":651,"config":665},{"href":653,"dataGaName":654,"dataGaLocation":470},{"text":656,"config":667},{"dataGaName":658,"dataGaLocation":470,"id":659,"isOneTrustButton":103},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[673],{"_path":674,"_dir":675,"_draft":6,"_partial":6,"_locale":7,"content":676,"config":680,"_id":682,"_type":26,"title":18,"_source":28,"_file":683,"_stem":684,"_extension":31},"/en-us/blog/authors/fabio-akita","authors",{"name":18,"config":677},{"headshot":678,"ctfId":679},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Fabio-Akita",{"template":681},"BlogAuthor","content:en-us:blog:authors:fabio-akita.yml","en-us/blog/authors/fabio-akita.yml","en-us/blog/authors/fabio-akita",{"_path":686,"_dir":34,"_draft":6,"_partial":6,"_locale":7,"header":687,"eyebrow":688,"blurb":689,"button":690,"secondaryButton":694,"_id":696,"_type":26,"title":697,"_source":28,"_file":698,"_stem":699,"_extension":31},"/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":42,"config":691},{"href":692,"dataGaName":45,"dataGaLocation":693},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":47,"config":695},{"href":49,"dataGaName":50,"dataGaLocation":693},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1753475342885]