[{"data":1,"prerenderedAt":717},["ShallowReactive",2],{"/en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/":3,"navigation-en-us":33,"banner-en-us":462,"footer-en-us":479,"Marco Lenzo":689,"next-steps-en-us":702},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":23,"_id":26,"_type":27,"title":28,"_source":29,"_file":30,"_stem":31,"_extension":32},"/en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Spring Boot delivery with GitLab CI and Kubernetes","Create a Continuous Delivery pipeline to deploy a Spring Boot app with GitLab CI and Kubernetes to Google Cloud Container Engine","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749672314/Blog/Hero%20Images/dew-leaf.jpg","https://about.gitlab.com/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Continuous delivery of a Spring Boot application with GitLab CI and Kubernetes\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Marco Lenzo\"}],\n        \"datePublished\": \"2016-12-14\",\n      }",{"title":17,"description":10,"authors":18,"heroImage":11,"date":20,"body":21,"category":22},"Continuous delivery of a Spring Boot application with GitLab CI and Kubernetes",[19],"Marco Lenzo","2016-12-14","[Continuous integration, continuous deployment and continuous\ndelivery](/topics/ci-cd/) are increasingly popular topics among modern\ndevelopment teams. Together they enable a team to build, test and deploy the\nsource code at any commit. The main benefit of these approaches is the\nability to release more quality code more frequently through the means of\nautomated pipelines. The tough part is building such pipelines. There is a\nmyriad of tools available which we would need to choose, learn, install,\nintegrate, and maintain.\n\n\nRecently, I literally fell in love with [GitLab](https://gitlab.com/)! It\noffers a fully featured ecosystem of tools which enable us to create an\nautomated pipeline in minutes! From source control to issue tracking and CI,\nwe find everything under one roof, fully integrated and ready to use.\n\n\n\u003C!-- more -->\n\n\nIn this tutorial, we will create a [Spring\nBoot](https://projects.spring.io/spring-boot/) application built, tested,\nand deployed with [GitLab CI](/solutions/continuous-integration/) on a\n[Kubernetes](http://kubernetes.io/) cluster.\n\n\n## What are Spring Boot and Kubernetes?\n\n\nSpring Boot (sometimes called Java Spring Boot) is the leading [microservice\nchassis](http://microservices.io/patterns/microservice-chassis.html) for\nJava. It allows a developer to build a production-grade stand-alone\napplication, like a typical\n[CRUD](https://en.wikipedia.org/wiki/Create,_read,_update_and_delete)\napplication exposing a [RESTful\nAPI](https://en.wikipedia.org/wiki/Representational_state_transfer), with\nminimal configuration, reducing the learning curve required for using the\n[Spring Framework](https://spring.io/) drastically.\n\n\nKubernetes is an open-source container orchestrator inspired by [Google\nBorg](http://static.googleusercontent.com/media/research.google.com/en//pubs/archive/43438.pdf)\nthat schedules, scales and manages containerized applications.\n\n\n\n## Create a GitLab project\n\n\nLet's start by [creating a new project](https://gitlab.com/projects/new) in\nGitLab named `actuator-sample`. Then we follow the command line instructions\ndisplayed in the project's home page to clone the repository on our machine\nand perform the first commit.\n\n\n```shell\n\ngit clone git@gitlab.com:marcolenzo/actuator-sample.git\n\ncd actuator-sample\n\ntouch README.md\n\ngit add README.md\n\ngit commit -m \"add README\"\n\ngit push -u origin master\n\n```\n\n\nAlways replace `marcolenzo` with your own GitLab username whenever copying a\nsnippet of code from this tutorial.\n\n{: .alert .alert-info}\n\n\n## Create a Spring Boot application\n\n\nTo bootstrap the Spring Boot application we navigate to the [Spring\nInitializr](https://start.spring.io) web page and generate a **Maven\nProject** with the pre-selected Spring Boot **Version**.\n[Maven](https://maven.apache.org/index.html) is a project management tool\ncommonly used in Java projects to define dependencies and the build\nlifecycle. We leave `com.example` as **Group** and set `actuator-sample` as\nthe **Artifact** name. We select the `Web` dependency, which supports full\nstack web development with [Tomcat](http://tomcat.apache.org/) and [Spring\nMVC](http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html),\nand the `Actuator` dependency which implements some production-grade\nfeatures useful for monitoring and managing our application like\nhealth-checks and HTTP requests traces.\n\n\nFinally, we generate the project and a Zip file named `actuator-sample.zip`\nwill be downloaded to our machine.\n\n\n![Spring\nInitializr](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/initializr.png){:\n.shadow}\n\n\nWe can now unzip the archive and launch the application immediately. Spring\nInitializr has already created everything for us. We just need to have a\n[Java JDK](http://openjdk.java.net/install/) 1.7 or later installed on our\nmachine and the `JAVA_HOME` environment variable set accordingly.\n[OpenJDK](http://openjdk.java.net/) is the preferred option for most Linux\ndistributions since it is readily available on their repositories. You can\nalternatively install [Oracle\nJDK](http://www.oracle.com/technetwork/java/javase/downloads/index.html) if\nit is a strict requirement for your team.\n\n\n\n```shell\n\n### Installing OpenJDK 8 on Debian, Ubuntu, etc.\n\n\nsudo apt-get install openjdk-8-jre\n\n\n### Installing OpenJDK 8 on Fedora, Oracle Linux, Red Hat Enteprise, CentOS,\netc.\n\n\nsu -c \"yum install java-1.8.0-openjdk\"\n\n\n### Setting the JAVA_HOME environment variable\n\n\nexport JAVA_HOME=/path/to/your/java/home # e.g.\n/usr/lib/jvm/java-8-openjdk-amd64/\n\n\n### Extracting and launching the application\n\n\n~/git/actuator-sample$ unzip ~/Downloads/actuator-sample.zip -d ../\n\n~/git/actuator-sample$ ./mvnw spring-boot:run\n\n\n[...]\n\n\n2016-12-02 22:41:14.376  INFO 10882 --- [           main]\ns.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080\n(http)\n\n2016-12-02 22:41:14.420  INFO 10882 --- [           main]\ncom.example.ActuatorSampleApplication    : Started ActuatorSampleApplication\nin 17.924 seconds (JVM running for 87.495)\n\n```\n\n\nThe application is up and running and we did not write one line of code!\nSpring Boot is opinionated and auto-configures the application with sane\ndefault values and beans. It also scans the classpath for known dependencies\nand initializes them. In our case, we immediately enjoy all the\nproduction-grade services offered by [Spring\nActuator](http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html).\n\n\n```shell\n\n~$ curl http://localhost:8080/health\n\n{\"status\":\"UP\",\"diskSpace\":{\"status\":\"UP\",\"total\":981190307840,\"free\":744776503296,\"threshold\":10485760}}\n\n```\n\n\nIf you wish to learn Spring Boot in greater detail, have a look at their\n[reference\ndocumentation](http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/)\nand [guides](https://spring.io/guides).\n\n{: .alert .alert-info}\n\n\nIt is time to commit our changes and push them to `origin`. To simplify\nthings a bit, we commit directly on `master` without using [feature\nbranches](https://docs.gitlab.com/ee/topics/gitlab_flow.html#github-flow-as-a-simpler-alternative)\nsince collaboration is not the focus of this tutorial. Later, we will use\n[environment\nbranches](https://docs.gitlab.com/ee/topics/gitlab_flow.html#environment-branches-with-gitlab-flow)\nas specified in the [GitLab\nFlow](https://docs.gitlab.com/ee/topics/gitlab_flow.html) to deploy to\ndifferent environments selectively, e.g. staging and production. If you are\nnot familiar with the [GitLab Flow](/solutions/gitlab-flow/), I strongly\nrecommend you to read its documentation.\n\n\n```shell\n\ngit add --all\n\ngit commit -m \"Creates actuator-example application\"\n\ngit push origin master\n\n```\n\n\n## Creating a continuous delivery pipeline with GitLab CI\n\n\nWhile our code is now safe on GitLab, we still need to automate its\nintegration and deployment. We need to verify each commit with an automated\nbuild and set of tests in order to discover issues as early as possible and,\nif the build is successful, deploy to a target environment. A few years ago,\nour only option was to install, configure and maintain a CI Server like\n[Jenkins](https://jenkins.io/) and possibly automate our deployment with a\nset of bash scripts. While the number of options has grown significantly,\nwhether hosted or on the cloud, we still need to find a way to integrate our\nsource control system with the CI Server of our choice.\n\n\nNot anymore though! GitLab has [fully integrated CI and CD\nPipelines](/topics/ci-cd/) in its offering, allowing us to [build, test and\ndeploy](/topics/version-control/what-is-gitlab-flow/) our code with ease.\n\n\nFor the purpose of this tutorial we will deploy to the [Google Cloud\nContainer Engine](https://cloud.google.com/container-engine/) which is a\ncluster management and orchestration system built on the open source\n[Kubernetes](http://kubernetes.io/). Kubernetes is supported by all main\ncloud providers and can be [easily installed on any Linux\nserver](http://kubernetes.io/docs/getting-started-guides/kubeadm/) in\nminutes. That said, we will be able to re-use this configuration virtually\non any environment running Kubernetes.\n\n\nBefore we can proceed to the creation of the pipeline, we need to add a\ncouple of files to our repository to package our application as a Docker\ncontainer and to describe the target deployment in Kubernetes terms.\n\n\n### Packaging a Spring Boot application as a Docker container\n\n\nLet's start by creating the `Dockerfile` in the root directory of our\nproject.\n\n\n```shell\n\nFROM openjdk:8u111-jdk-alpine\n\nVOLUME /tmp\n\nADD /target/actuator-sample-0.0.1-SNAPSHOT.jar app.jar\n\nENTRYPOINT\n[\"java\",\"-Djava.security.egd=file:/dev/./urandom\",\"-jar\",\"/app.jar\"]\n\n```\n\n\nThe `FROM` keyword defines the base Docker image of our container. We chose\n[OpenJDK](http://openjdk.java.net/) installed on [Alpine\nLinux](https://alpinelinux.org/) which is a lightweight Linux distribution.\nThe `VOLUME` instruction creates a mount point with the specified name and\nmarks it as holding externally mounted volumes from the native host or other\ncontainers. `ADD` copies the executable JAR generated during the build to\nthe container root directory. Finally `ENTRYPOINT` defines the command to\nexecute when the container is started. Since Spring Boot produces an\nexecutable JAR with embedded Tomcat, the command to execute is simply `java\n-jar app.jar`. The additional flag `java.security.edg=file:/dev/./urandom`\nis used to speed up the application start-up and avoid possible freezes. By\ndefault, Java uses `/dev/random` to seed its `SecureRandom` class which is\nknown to block if its entropy pool is empty.\n\n\nTime to commit.\n\n\n```shell\n\ngit add Dockerfile\n\ngit commit -m \"Adds Dockerfile\"\n\ngit push origin master\n\n```\n\n\n### Define the Kubernetes deployment\n\n\nLet's create a file named `deployment.yml` in the root directory of our\nproject.\n\n\n```yml\n\napiVersion: extensions/v1beta1\n\nkind: Deployment\n\nmetadata:\n  name: actuator-sample\nspec:\n  replicas: 2\n  template:\n    metadata:\n      labels:\n        app: actuator-sample\n    spec:\n      containers:\n      - name: actuator-sample\n        image: registry.gitlab.com/marcolenzo/actuator-sample\n        imagePullPolicy: Always\n        ports:\n        - containerPort: 8080\n      imagePullSecrets:\n        - name: registry.gitlab.com\n```\n\n\nThis is the definition of a Kubernetes\n[`Deployment`](http://kubernetes.io/docs/user-guide/deployments/) named\n`actuator-sample`. The `replicas` element defines the target number of\n[`Pods`](http://kubernetes.io/docs/user-guide/pods/). Kubernetes performs\nautomated binpacking and self-healing of the system to comply with the\ndeployment specifications while achieving optimal utilization of compute\nresources. A Pod can be composed of multiple containers. In this scenario,\nwe only include the `actuator-sample` image stored on our private [GitLab\nContainer Registry](/blog/gitlab-container-registry/). For this reason, we\nneed to set an entry under the `imagePullSecrets` which is used to\nauthenticate to the GitLab Container Registry.\n\n\nFor a detailed explanation of Kubernetes resources and concepts refer to the\n[official documentation](http://kubernetes.io/).\n\n{: .alert .alert-info}\n\n\nTime to commit again and we are ready to define our GitLab CI pipeline.\n\n\n```shell\n\ngit add deployment.yml\n\ngit commit -m \"Adds Kubernetes Deployment definition\"\n\ngit push origin master\n\n```\n\n\n### Creating the GitLab CI pipeline\n\n\nIn order to make use of [GitLab CI](/solutions/continuous-integration/) we\nneed to add the [`.gitlab-ci.yml`](https://docs.gitlab.com/ee/ci/yaml/)\nconfiguration file to the root directory of our repository. This file is\nused by [GitLab Runners](https://docs.gitlab.com/ee/ci/runners/) to manage\nour project's builds and deployments. Therein we can define an unlimited\nnumber of [Jobs](https://docs.gitlab.com/ee/ci/jobs/) and their role in the\nwhole build lifecycle.\n\n\n```yml\n\nimage: docker:latest\n\nservices:\n  - docker:dind\n\nvariables:\n  DOCKER_DRIVER: overlay\n  SPRING_PROFILES_ACTIVE: gitlab-ci\n\nstages:\n  - build\n  - package\n  - deploy\n\nmaven-build:\n  image: maven:3-jdk-8\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n\ndocker-build:\n  stage: package\n  script:\n  - docker build -t registry.gitlab.com/marcolenzo/actuator-sample .\n  - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com\n  - docker push registry.gitlab.com/marcolenzo/actuator-sample\n\nk8s-deploy:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-sample\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml\n```\n\n\nLet's break the file in pieces to understand what is going on.\n\n\n#### Image and Services\n\n\n```yml\n\nimage: docker:latest\n\nservices:\n  - docker:dind\n```\n\n\nThe [GitLab Runner](https://docs.gitlab.com/ee/ci/runners/) can [use Docker\nimages](https://docs.gitlab.com/ee/ci/docker/using_docker_images.html) to\nsupport our pipelines. The [`image`\nelement](https://docs.gitlab.com/ee/ci/yaml/#image) defines the name of the\nDocker image we want to use. Valid images are those hosted in the local\nDocker Engine or on [Docker Hub](https://hub.docker.com/). The `services`\nelement defines additional Docker images which are linked to the main\ncontainer. In our case the main container is a plain Docker image while the\nlinked container is enabled for running Docker in Docker.\n\n\n#### Variables\n\n\n```yml\n\nvariables:\n  DOCKER_DRIVER: overlay\n  SPRING_PROFILES_ACTIVE: gitlab-ci\n```\n\n\nThis is the definition of\n[`variables`](https://docs.gitlab.com/ee/ci/yaml/#variables) to be set on\nour build environment. The `DOCKER_DRIVER` signals the Docker Engine which\nstorage driver to use. We use `overlay` for [performance\nreasons](https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#using-the-overlayfs-driver).\nThe `SPRING_PROFILES_ACTIVE` is very useful when dealing with Spring Boot\napplications. It activates [Spring\nProfiles](http://docs.spring.io/autorepo/docs/spring-boot/current/reference/html/boot-features-profiles.html),\nwhich provide a way to segregate parts of our application configuration and\nmake it available only in certain environments. For instance, we can define\ndifferent database URIs per environment, e.g. `localhost` when running on\nthe developer machine and `mongo` when running within GitLab CI.\n\n\n#### Stages\n\n\n```yml\n\nstages:\n  - build\n  - package\n  - deploy\n```\n\n\nThe [`stages` element](https://docs.gitlab.com/ee/ci/yaml/#stages) defines\nthe lifecycle of our build. We associate each\n[job](https://docs.gitlab.com/ee/ci/jobs/) with one stage. All jobs within a\nstage are run in parallel and stages are triggered sequentially in the order\nwe define them, i.e. the next stage is initiated only when the previous one\nis complete.\n\n\n#### The `maven-build` job\n\n\n```yml\n\nmaven-build:\n  image: maven:3-jdk-8\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n```\n\n\nThis is a job definition. Jobs can have any name except keywords. Have a\nlook at the `.gitlab-ci.yml`\n[documentation](https://docs.gitlab.com/ee/ci/yaml/) for the complete list\nof keywords.\n\n\nThe scope of this job is to perform a\n[Maven](https://maven.apache.org/index.html) build. For this reason, we\ndefine the `maven:3-jdk-8` as the Docker image on which this job should\nexecute. This image comes with Maven 3 and the Java JDK 8 pre-installed for\nus.\n\n\nWe then specify `build` as the `stage` of this job. Jobs associated with the\nsame stage run concurrently. This is extremely useful if you need to\ncross-compile your application. For instance, if we wanted to compile and\ntest our application also on Java JDK 7, we could simply create another job\nwith a different name and use the image `maven:3-jdk-7`.\n\n\n```yml\n\nmaven-test-jdk-7:\n  image: maven:3-jdk-7\n  stage: build\n  script: \"mvn package -B\"\n  artifacts:\n    paths:\n      - target/*.jar\n```\n\n\nAs previously said, the `maven-test-jdk-7` job runs in parallel with the\n`maven-build`. Hence, it does not have an impact on the pipeline execution\ntime.\n\n\nThe [`script`](https://docs.gitlab.com/ee/ci/yaml/#script) is a shell\ncommand to be executed by the GitLab Runner. The `mvn package -B` triggers a\nnon-interactive Maven build up to the `package` phase. This phase is\nspecific to the [Maven build\nlifecycle](https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html)\nand it includes also the `validate`, `compile` and `test` phases. That means\nthat our Maven project will be validated, compiled and (unit) tested as\nwell. Tests are to be included in the `src/test/java` folder. In our\nspecific case, Spring Initializr has already created a unit test which\nverifies that the application context loads without errors. We are free to\nadd as many unit tests as we like. Finally, the `package` phase creates the\nexecutable JAR.\n\n\nTo persist the executable JAR and share it across jobs, we specify job\n[`artifacts`](https://docs.gitlab.com/ee/ci/yaml/#artifacts). These are\nfiles or directories that are attached to the build after success and made\ndownloadable from the UI in the Pipelines screen.\n\n\n![Downloading artifacts from\npipelines](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/artifacts.png){:\n.shadow}\n\n\n\n#### The `docker-build` job\n\n\n```yml\n\ndocker-build:\n  stage: package\n  script:\n  - docker build -t registry.gitlab.com/marcolenzo/actuator-sample .\n  - docker login -u gitlab-ci-token -p $CI_BUILD_TOKEN registry.gitlab.com\n  - docker push registry.gitlab.com/marcolenzo/actuator-sample\n```\n\n\nThe `docker-build` job packages the application into a Docker container. We\ndefine `package` as the build `stage` since we need the `maven-build` job to\nproduce the executable JAR beforehand.\n\n\nThe scripts are a typical sequence of `docker` commands used to build an\nimage, log in to a private registry and push the image to it. We will be\npushing images to the [GitLab Container\nRegistry](/blog/gitlab-container-registry/).\n\n\nThe\n[`$CI_BUILD_TOKEN`](https://docs.gitlab.com/ee/user/project/new_ci_build_permissions_model.html#container-registry)\nis a pre-defined variable which is injected by GitLab CI into our build\nenvironment automatically. It is used to log in to the GitLab Container\nRegistry.\n\n\nFor a complete list of pre-defined variables, have a look at the [variables\ndocumentation](https://docs.gitlab.com/ee/ci/variables/).\n\n{: .alert .alert-info}\n\n\n#### The `k8s-deploy` job\n\n\nThis job is responsible for deploying our application to the [Google\nKubernetes Engine](https://cloud.google.com/container-engine/). I purposely\ndecided to make use of the [Google Cloud\nSDK](https://cloud.google.com/sdk/gcloud/) (`gcloud`) because it gives us\nthe possibility to programmatically create and manage Google Container\nEngine clusters and other products of the Google Cloud ecosystem. In this\ntutorial, we will simplify things a bit by creating the Google Container\nEngine cluster beforehand through the GUI.\n\n\nFirst, we create a Google Cloud Project named `actuator-sample`. Take note\nof the `Project ID` since it sometimes differs from the project name we\nspecify. Then we create a Google Kubernetes Engine cluster named\n`actuator-sample` as well. We can choose any machine type and any number of\nnodes. For the purpose of this tutorial one node and a small machine are\nsufficient. Let's take note of the `zone`.\n\n\n![Create a container\ncluster](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/create-gce-cluster.png){:\n.shadow}\n\n\nFinally we need to create a service account which is necessary to perform a\nnon-interactive login with `gcloud`. Navigate to Google Cloud **API\nManager** > **Credentials** > **Create Credentials** and create a JSON key\nfor the `Compute Engine default service account`.\n\n\nWe can now analyze the configuration.\n\n\n```yml\n\nk8s-deploy:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json # Google Cloud service account key\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml\n```\n\n\nWe use the `google/cloud-sdk` image for this process since it comes\npreloaded with `gcloud` and all components and dependencies of the Google\nCloud SDK including alpha and beta components. We obviously chose `deploy`\nas the `stage` since we want our application to be packaged beforehand and\nits container pushed to the GitLab Container Registry. Then we execute a set\nof scripts.\n\n\nThe `echo \"$GOOGLE_KEY\" > key.json` script injects the Google Cloud service\naccount key in the container. `$GOOGLE_KEY` is a Secure Variable having the\ncontent of the Google Cloud service account key as its value. [Secure\nVariables](https://docs.gitlab.com/ee/ci/variables/#user-defined-variables-secure-variables)\nare user-defined variables that should not be shown in the `.gitlab-ci.yml`\nfile. They are set per project by navigating to **Project** > **Variables**\n> **Add Variable** in GitLab.\n\n\n![Secure\nVariables](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/secure-variables.png){:\n.shadow}\n\n\nThe `gcloud auth activate-service-account --key-file key.json` script\nperforms the non-interactive authentication process. The `gcloud config set\n...` scripts are selecting the target project, zone and cluster. Make sure\nthese values correspond to those you jotted down before. The `gcloud\ncontainer clusters get-credentials actuator-example` script downloads the\n`kubectl` configuration file. If we wanted to use Kubernetes on another\ncloud provider or custom installation, we would source the `kubectl`\nconfiguration `~/.kube/config` without the need to interact with `gcloud`.\n\n\nThe `kubectl create secret docker-registry ...` script creates the\n`imagePullSecret` we had defined in the `deployment.yml`. This is used by\nKubernetes to authenticate with our private GitLab Container Registry and\ndownload the container images. The `kubectl delete secret` is necessary\nbecause the Kubernetes API is lacking the `replace` operation for\n`docker-registry` secrets. In a real-world scenario, I would suggest\nhandling [Kubernetes secrets](http://kubernetes.io/docs/user-guide/secrets/)\nthat can affect multiple pipelines (such as a password for a private Docker\nregistry) in a separate pipeline or through configuration management tools\nlike [Ansible](https://www.ansible.com/), [Salt](https://saltstack.com/),\n[Puppet](https://puppet.com/) or [Chef](https://www.chef.io/). The reason is\nthat such secrets should be rotated periodically for security reasons and\nupdated in each GitLab project using them. There is also the risk of\ninterference between pipelines because of the `kubectl delete` command. Note\nthat `$REGISTRY_PASSWD` is another Secure Variable.\n\n\nTime to check if everything is in order on our cluster.\n\n\n```shell\n\n$ kubectl get deployments\n\nNAME              DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE\n\nactuator-sample   2         2         2            2           2m\n\n$ kubectl get pods\n\nNAME                               READY     STATUS    RESTARTS   AGE\n\nactuator-sample-3641958612-3e5xy   1/1       Running   0          2m\n\nactuator-sample-5542343546-fr4gh   1/1       Running   0          2m\n\n```\n\n\n![Kubernetes](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/kubernetes.png){:\n.shadow}\n\n\nDeployed!\n\n\n#### GitLab Environments\n\n\nBefore concluding the tutorial, we will learn about [GitLab\nEnvironments](https://docs.gitlab.com/ee/ci/environments/index.html) which\nenable us to track environments and deployments.\n\n\nLet's refactor the `k8s-deploy` job and split it in two. One job will target\nthe staging environment and the other the production environment.\n\n\n```yml\n\nk8s-deploy-staging:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml --namespace=staging\n  environment:\n    name: staging\n    url: https://example.staging.com\n  only:\n  - master\n\nk8s-deploy-production:\n  image: google/cloud-sdk\n  stage: deploy\n  script:\n  - echo \"$GOOGLE_KEY\" > key.json\n  - gcloud auth activate-service-account --key-file key.json\n  - gcloud config set compute/zone europe-west1-c\n  - gcloud config set project actuator-sample\n  - gcloud config set container/use_client_certificate True\n  - gcloud container clusters get-credentials actuator-example\n  - kubectl delete secret registry.gitlab.com\n  - kubectl create secret docker-registry registry.gitlab.com --docker-server=https://registry.gitlab.com --docker-username=marcolenzo --docker-password=$REGISTRY_PASSWD --docker-email=lenzo.marco@gmail.com\n  - kubectl apply -f deployment.yml --namespace=production\n  environment:\n    name: production\n    url: https://example.production.com\n  when: manual\n  only:\n  - production\n```\n\n\nThe `environment` keyword associates the job with a specific environment\nwhile the `url` element is used to generate a handy hyperlink to our\napplication on the GitLab Environments page (found under your project's\n`Pipelines > Environments`). The `only` keyword signals to GitLab CI that\nthe job should be executed only when the pipeline is building the listed\nbranches. Finally, `when: manual` is used to turn the job execution from\nautomatic to manual. Turning the execution of this job to `automatic` would\nproject us in the world of [Continuous\nDeployment](/blog/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-deployment)\nrather than [Continuous\nDelivery](/blog/continuous-integration-delivery-and-deployment-with-gitlab/#continuous-delivery).\nFrom a Kubernetes perspective, we are making use of `namespaces` to\nsegregate the different environments.\n\n\nBy committing on `master` and `production` we [trigger a pipeline per\nenvironment](/blog/ci-deployment-and-environments/). As mentioned\nbefore, we are not making use of any collaboration tool because it is out of\nthe scope of this tutorial. In real-world scenarios, we would use [merge\nrequests](/topics/version-control/what-is-gitlab-flow/#merge-request) with\n[Review Apps](https://docs.gitlab.com/ee/ci/review_apps/) to move code across\nbranches. Merge requests allow the team to review and discuss the changes\nbefore they get merged into the target branch. [Review\nApps](https://docs.gitlab.com/ee/ci/review_apps/) take that one step further by\nspinning up dynamic environments for our merge requests, offering the team\naccess to a deployed instance of our application without the need of\nchecking out the branch. This is extremely useful not only for non-technical\nmembers of the team, but also to collaborators and project managers to\npreview the changes without having to clone and install the app and its\ndependencies when evaluating a proposal.\n\n\n```shell\n\ngit commit -am \"Showcasing Pipelines\"\n\ngit push origin master\n\ngit checkout -b production\n\ngit push origin production\n\n```\n\n![Pipelines](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/pipelines.png){:\n.shadow}\n\n\nThe Pipelines screen details all pipeline executions. We can gather\ninformation about the branch and the individual result of each stage. In the\ncase of the `production` pipeline the `k8s-deploy-production` is not\nexecuted automatically as expected but can be triggered from the GUI from\nwhere we can also download the build artifacts.\n\n\n![Environments](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/environments.png){:\n.shadow}\n\n\nEnvironments are listed on a separate page, from which it is possible to\nredeploy the latest version of an environment or to roll back to a\nparticular version of the environment by accessing the relative details\npage.\n\n\n![Rollbacks](https://about.gitlab.com/images/blogimages/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes/rollbacks.png){:\n.shadow}\n\n\n\n## Conclusion\n\n\nIn this tutorial, we were able to create a [Continuous\nDelivery](https://en.wikipedia.org/wiki/Continuous_delivery) pipeline with\nease thanks to the suite of [GitLab](/) products that supported us at every\nstage. [Spring Boot](https://projects.spring.io/spring-boot/) gave us\nagility by auto-configuring the application context and offering\nproduction-grade services out of the box.\n[Kubernetes](http://kubernetes.io/) abstracted us from the compute resources\nand orchestration duties allowing us to define only the desired deployment\nstate. [GitLab CI](/solutions/continuous-integration/) was the core engine of\nour pipeline. Its declarative\n[`.gitlab-ci.yml`](https://docs.gitlab.com/ee/ci/yaml/) file allowed us to\ndefine, version and manage our pipelines while the GUI gave us full\nvisibility and control.\n\n\nWhile this is a basic example, it clearly shows the immense benefits any\nteam or company can gain by using the unified GUI of GitLab for issues, code\nreview, CI and CD.\n\n\n## About Guest Author\n\n\n[Marco Lenzo](https://twitter.com/marco_lenzo) is a Software Architect\nalways up for a challenge. He has expertise in transaction processing and\nplatform as a service (PaaS). Java, Spring, Go and Kubernetes are currently\nhis bread and butter.\n\n\n\u003C!-- closes https://gitlab.com/gitlab-com/blog-posts/issues/309 -->\n\n\u003C!-- cover image: https://unsplash.com/photos/G86MS2ZsiJA -->\n\n\n\u003Cstyle>\n  .h4 {\n    font-weight: bold;\n  }\n\u003C/style>\n","engineering",{"slug":24,"featured":6,"template":25},"continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","BlogPost","content:en-us:blog:continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes.yml","yaml","Continuous Delivery Of A Spring Boot Application With Gitlab Ci And Kubernetes","content","en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes.yml","en-us/blog/continuous-delivery-of-a-spring-boot-application-with-gitlab-ci-and-kubernetes","yml",{"_path":34,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"data":36,"_id":458,"_type":27,"title":459,"_source":29,"_file":460,"_stem":461,"_extension":32},"/shared/en-us/main-navigation","en-us",{"logo":37,"freeTrial":42,"sales":47,"login":52,"items":57,"search":389,"minimal":420,"duo":439,"pricingDeployment":448},{"config":38},{"href":39,"dataGaName":40,"dataGaLocation":41},"/","gitlab logo","header",{"text":43,"config":44},"Get free trial",{"href":45,"dataGaName":46,"dataGaLocation":41},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":48,"config":49},"Talk to sales",{"href":50,"dataGaName":51,"dataGaLocation":41},"/sales/","sales",{"text":53,"config":54},"Sign in",{"href":55,"dataGaName":56,"dataGaLocation":41},"https://gitlab.com/users/sign_in/","sign in",[58,102,200,205,310,370],{"text":59,"config":60,"cards":62,"footer":85},"Platform",{"dataNavLevelOne":61},"platform",[63,69,77],{"title":59,"description":64,"link":65},"The most comprehensive AI-powered DevSecOps Platform",{"text":66,"config":67},"Explore our Platform",{"href":68,"dataGaName":61,"dataGaLocation":41},"/platform/",{"title":70,"description":71,"link":72},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":73,"config":74},"Meet GitLab Duo",{"href":75,"dataGaName":76,"dataGaLocation":41},"/gitlab-duo/","gitlab duo ai",{"title":78,"description":79,"link":80},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":81,"config":82},"Learn more",{"href":83,"dataGaName":84,"dataGaLocation":41},"/why-gitlab/","why gitlab",{"title":86,"items":87},"Get started with",[88,93,98],{"text":89,"config":90},"Platform Engineering",{"href":91,"dataGaName":92,"dataGaLocation":41},"/solutions/platform-engineering/","platform engineering",{"text":94,"config":95},"Developer Experience",{"href":96,"dataGaName":97,"dataGaLocation":41},"/developer-experience/","Developer experience",{"text":99,"config":100},"MLOps",{"href":101,"dataGaName":99,"dataGaLocation":41},"/topics/devops/the-role-of-ai-in-devops/",{"text":103,"left":104,"config":105,"link":107,"lists":111,"footer":182},"Product",true,{"dataNavLevelOne":106},"solutions",{"text":108,"config":109},"View all Solutions",{"href":110,"dataGaName":106,"dataGaLocation":41},"/solutions/",[112,137,161],{"title":113,"description":114,"link":115,"items":120},"Automation","CI/CD and automation to accelerate deployment",{"config":116},{"icon":117,"href":118,"dataGaName":119,"dataGaLocation":41},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[121,125,129,133],{"text":122,"config":123},"CI/CD",{"href":124,"dataGaLocation":41,"dataGaName":122},"/solutions/continuous-integration/",{"text":126,"config":127},"AI-Assisted Development",{"href":75,"dataGaLocation":41,"dataGaName":128},"AI assisted development",{"text":130,"config":131},"Source Code Management",{"href":132,"dataGaLocation":41,"dataGaName":130},"/solutions/source-code-management/",{"text":134,"config":135},"Automated Software Delivery",{"href":118,"dataGaLocation":41,"dataGaName":136},"Automated software delivery",{"title":138,"description":139,"link":140,"items":145},"Security","Deliver code faster without compromising security",{"config":141},{"href":142,"dataGaName":143,"dataGaLocation":41,"icon":144},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[146,151,156],{"text":147,"config":148},"Application Security Testing",{"href":149,"dataGaName":150,"dataGaLocation":41},"/solutions/application-security-testing/","Application security testing",{"text":152,"config":153},"Software Supply Chain Security",{"href":154,"dataGaLocation":41,"dataGaName":155},"/solutions/supply-chain/","Software supply chain security",{"text":157,"config":158},"Software Compliance",{"href":159,"dataGaName":160,"dataGaLocation":41},"/solutions/software-compliance/","software compliance",{"title":162,"link":163,"items":168},"Measurement",{"config":164},{"icon":165,"href":166,"dataGaName":167,"dataGaLocation":41},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[169,173,177],{"text":170,"config":171},"Visibility & Measurement",{"href":166,"dataGaLocation":41,"dataGaName":172},"Visibility and Measurement",{"text":174,"config":175},"Value Stream Management",{"href":176,"dataGaLocation":41,"dataGaName":174},"/solutions/value-stream-management/",{"text":178,"config":179},"Analytics & Insights",{"href":180,"dataGaLocation":41,"dataGaName":181},"/solutions/analytics-and-insights/","Analytics and insights",{"title":183,"items":184},"GitLab for",[185,190,195],{"text":186,"config":187},"Enterprise",{"href":188,"dataGaLocation":41,"dataGaName":189},"/enterprise/","enterprise",{"text":191,"config":192},"Small Business",{"href":193,"dataGaLocation":41,"dataGaName":194},"/small-business/","small business",{"text":196,"config":197},"Public Sector",{"href":198,"dataGaLocation":41,"dataGaName":199},"/solutions/public-sector/","public sector",{"text":201,"config":202},"Pricing",{"href":203,"dataGaName":204,"dataGaLocation":41,"dataNavLevelOne":204},"/pricing/","pricing",{"text":206,"config":207,"link":209,"lists":213,"feature":297},"Resources",{"dataNavLevelOne":208},"resources",{"text":210,"config":211},"View all resources",{"href":212,"dataGaName":208,"dataGaLocation":41},"/resources/",[214,247,269],{"title":215,"items":216},"Getting started",[217,222,227,232,237,242],{"text":218,"config":219},"Install",{"href":220,"dataGaName":221,"dataGaLocation":41},"/install/","install",{"text":223,"config":224},"Quick start guides",{"href":225,"dataGaName":226,"dataGaLocation":41},"/get-started/","quick setup checklists",{"text":228,"config":229},"Learn",{"href":230,"dataGaLocation":41,"dataGaName":231},"https://university.gitlab.com/","learn",{"text":233,"config":234},"Product documentation",{"href":235,"dataGaName":236,"dataGaLocation":41},"https://docs.gitlab.com/","product documentation",{"text":238,"config":239},"Best practice videos",{"href":240,"dataGaName":241,"dataGaLocation":41},"/getting-started-videos/","best practice videos",{"text":243,"config":244},"Integrations",{"href":245,"dataGaName":246,"dataGaLocation":41},"/integrations/","integrations",{"title":248,"items":249},"Discover",[250,255,259,264],{"text":251,"config":252},"Customer success stories",{"href":253,"dataGaName":254,"dataGaLocation":41},"/customers/","customer success stories",{"text":256,"config":257},"Blog",{"href":258,"dataGaName":5,"dataGaLocation":41},"/blog/",{"text":260,"config":261},"Remote",{"href":262,"dataGaName":263,"dataGaLocation":41},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":265,"config":266},"TeamOps",{"href":267,"dataGaName":268,"dataGaLocation":41},"/teamops/","teamops",{"title":270,"items":271},"Connect",[272,277,282,287,292],{"text":273,"config":274},"GitLab Services",{"href":275,"dataGaName":276,"dataGaLocation":41},"/services/","services",{"text":278,"config":279},"Community",{"href":280,"dataGaName":281,"dataGaLocation":41},"/community/","community",{"text":283,"config":284},"Forum",{"href":285,"dataGaName":286,"dataGaLocation":41},"https://forum.gitlab.com/","forum",{"text":288,"config":289},"Events",{"href":290,"dataGaName":291,"dataGaLocation":41},"/events/","events",{"text":293,"config":294},"Partners",{"href":295,"dataGaName":296,"dataGaLocation":41},"/partners/","partners",{"backgroundColor":298,"textColor":299,"text":300,"image":301,"link":305},"#2f2a6b","#fff","Insights for the future of software development",{"altText":302,"config":303},"the source promo card",{"src":304},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":306,"config":307},"Read the latest",{"href":308,"dataGaName":309,"dataGaLocation":41},"/the-source/","the source",{"text":311,"config":312,"lists":314},"Company",{"dataNavLevelOne":313},"company",[315],{"items":316},[317,322,328,330,335,340,345,350,355,360,365],{"text":318,"config":319},"About",{"href":320,"dataGaName":321,"dataGaLocation":41},"/company/","about",{"text":323,"config":324,"footerGa":327},"Jobs",{"href":325,"dataGaName":326,"dataGaLocation":41},"/jobs/","jobs",{"dataGaName":326},{"text":288,"config":329},{"href":290,"dataGaName":291,"dataGaLocation":41},{"text":331,"config":332},"Leadership",{"href":333,"dataGaName":334,"dataGaLocation":41},"/company/team/e-group/","leadership",{"text":336,"config":337},"Team",{"href":338,"dataGaName":339,"dataGaLocation":41},"/company/team/","team",{"text":341,"config":342},"Handbook",{"href":343,"dataGaName":344,"dataGaLocation":41},"https://handbook.gitlab.com/","handbook",{"text":346,"config":347},"Investor relations",{"href":348,"dataGaName":349,"dataGaLocation":41},"https://ir.gitlab.com/","investor relations",{"text":351,"config":352},"Trust Center",{"href":353,"dataGaName":354,"dataGaLocation":41},"/security/","trust center",{"text":356,"config":357},"AI Transparency Center",{"href":358,"dataGaName":359,"dataGaLocation":41},"/ai-transparency-center/","ai transparency center",{"text":361,"config":362},"Newsletter",{"href":363,"dataGaName":364,"dataGaLocation":41},"/company/contact/","newsletter",{"text":366,"config":367},"Press",{"href":368,"dataGaName":369,"dataGaLocation":41},"/press/","press",{"text":371,"config":372,"lists":373},"Contact us",{"dataNavLevelOne":313},[374],{"items":375},[376,379,384],{"text":48,"config":377},{"href":50,"dataGaName":378,"dataGaLocation":41},"talk to sales",{"text":380,"config":381},"Get help",{"href":382,"dataGaName":383,"dataGaLocation":41},"/support/","get help",{"text":385,"config":386},"Customer portal",{"href":387,"dataGaName":388,"dataGaLocation":41},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":390,"login":391,"suggestions":398},"Close",{"text":392,"link":393},"To search repositories and projects, login to",{"text":394,"config":395},"gitlab.com",{"href":55,"dataGaName":396,"dataGaLocation":397},"search login","search",{"text":399,"default":400},"Suggestions",[401,403,407,409,413,417],{"text":70,"config":402},{"href":75,"dataGaName":70,"dataGaLocation":397},{"text":404,"config":405},"Code Suggestions (AI)",{"href":406,"dataGaName":404,"dataGaLocation":397},"/solutions/code-suggestions/",{"text":122,"config":408},{"href":124,"dataGaName":122,"dataGaLocation":397},{"text":410,"config":411},"GitLab on AWS",{"href":412,"dataGaName":410,"dataGaLocation":397},"/partners/technology-partners/aws/",{"text":414,"config":415},"GitLab on Google Cloud",{"href":416,"dataGaName":414,"dataGaLocation":397},"/partners/technology-partners/google-cloud-platform/",{"text":418,"config":419},"Why GitLab?",{"href":83,"dataGaName":418,"dataGaLocation":397},{"freeTrial":421,"mobileIcon":426,"desktopIcon":431,"secondaryButton":434},{"text":422,"config":423},"Start free trial",{"href":424,"dataGaName":46,"dataGaLocation":425},"https://gitlab.com/-/trials/new/","nav",{"altText":427,"config":428},"Gitlab Icon",{"src":429,"dataGaName":430,"dataGaLocation":425},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":427,"config":432},{"src":433,"dataGaName":430,"dataGaLocation":425},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":435,"config":436},"Get Started",{"href":437,"dataGaName":438,"dataGaLocation":425},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":440,"mobileIcon":444,"desktopIcon":446},{"text":441,"config":442},"Learn more about GitLab Duo",{"href":75,"dataGaName":443,"dataGaLocation":425},"gitlab duo",{"altText":427,"config":445},{"src":429,"dataGaName":430,"dataGaLocation":425},{"altText":427,"config":447},{"src":433,"dataGaName":430,"dataGaLocation":425},{"freeTrial":449,"mobileIcon":454,"desktopIcon":456},{"text":450,"config":451},"Back to pricing",{"href":203,"dataGaName":452,"dataGaLocation":425,"icon":453},"back to pricing","GoBack",{"altText":427,"config":455},{"src":429,"dataGaName":430,"dataGaLocation":425},{"altText":427,"config":457},{"src":433,"dataGaName":430,"dataGaLocation":425},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":463,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"title":464,"button":465,"image":470,"config":474,"_id":476,"_type":27,"_source":29,"_file":477,"_stem":478,"_extension":32},"/shared/en-us/banner","is now in public beta!",{"text":466,"config":467},"Try the Beta",{"href":468,"dataGaName":469,"dataGaLocation":41},"/gitlab-duo/agent-platform/","duo banner",{"altText":471,"config":472},"GitLab Duo Agent Platform",{"src":473},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":475},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":480,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"data":481,"_id":685,"_type":27,"title":686,"_source":29,"_file":687,"_stem":688,"_extension":32},"/shared/en-us/main-footer",{"text":482,"source":483,"edit":489,"contribute":494,"config":499,"items":504,"minimal":677},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":484,"config":485},"View page source",{"href":486,"dataGaName":487,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":490,"config":491},"Edit this page",{"href":492,"dataGaName":493,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":495,"config":496},"Please contribute",{"href":497,"dataGaName":498,"dataGaLocation":488},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":500,"facebook":501,"youtube":502,"linkedin":503},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[505,528,584,613,647],{"title":59,"links":506,"subMenu":511},[507],{"text":508,"config":509},"DevSecOps platform",{"href":68,"dataGaName":510,"dataGaLocation":488},"devsecops platform",[512],{"title":201,"links":513},[514,518,523],{"text":515,"config":516},"View plans",{"href":203,"dataGaName":517,"dataGaLocation":488},"view plans",{"text":519,"config":520},"Why Premium?",{"href":521,"dataGaName":522,"dataGaLocation":488},"/pricing/premium/","why premium",{"text":524,"config":525},"Why Ultimate?",{"href":526,"dataGaName":527,"dataGaLocation":488},"/pricing/ultimate/","why ultimate",{"title":529,"links":530},"Solutions",[531,536,538,540,545,550,554,557,561,566,568,571,574,579],{"text":532,"config":533},"Digital transformation",{"href":534,"dataGaName":535,"dataGaLocation":488},"/topics/digital-transformation/","digital transformation",{"text":147,"config":537},{"href":149,"dataGaName":147,"dataGaLocation":488},{"text":136,"config":539},{"href":118,"dataGaName":119,"dataGaLocation":488},{"text":541,"config":542},"Agile development",{"href":543,"dataGaName":544,"dataGaLocation":488},"/solutions/agile-delivery/","agile delivery",{"text":546,"config":547},"Cloud transformation",{"href":548,"dataGaName":549,"dataGaLocation":488},"/topics/cloud-native/","cloud transformation",{"text":551,"config":552},"SCM",{"href":132,"dataGaName":553,"dataGaLocation":488},"source code management",{"text":122,"config":555},{"href":124,"dataGaName":556,"dataGaLocation":488},"continuous integration & delivery",{"text":558,"config":559},"Value stream management",{"href":176,"dataGaName":560,"dataGaLocation":488},"value stream management",{"text":562,"config":563},"GitOps",{"href":564,"dataGaName":565,"dataGaLocation":488},"/solutions/gitops/","gitops",{"text":186,"config":567},{"href":188,"dataGaName":189,"dataGaLocation":488},{"text":569,"config":570},"Small business",{"href":193,"dataGaName":194,"dataGaLocation":488},{"text":572,"config":573},"Public sector",{"href":198,"dataGaName":199,"dataGaLocation":488},{"text":575,"config":576},"Education",{"href":577,"dataGaName":578,"dataGaLocation":488},"/solutions/education/","education",{"text":580,"config":581},"Financial services",{"href":582,"dataGaName":583,"dataGaLocation":488},"/solutions/finance/","financial services",{"title":206,"links":585},[586,588,590,592,595,597,599,601,603,605,607,609,611],{"text":218,"config":587},{"href":220,"dataGaName":221,"dataGaLocation":488},{"text":223,"config":589},{"href":225,"dataGaName":226,"dataGaLocation":488},{"text":228,"config":591},{"href":230,"dataGaName":231,"dataGaLocation":488},{"text":233,"config":593},{"href":235,"dataGaName":594,"dataGaLocation":488},"docs",{"text":256,"config":596},{"href":258,"dataGaName":5,"dataGaLocation":488},{"text":251,"config":598},{"href":253,"dataGaName":254,"dataGaLocation":488},{"text":260,"config":600},{"href":262,"dataGaName":263,"dataGaLocation":488},{"text":273,"config":602},{"href":275,"dataGaName":276,"dataGaLocation":488},{"text":265,"config":604},{"href":267,"dataGaName":268,"dataGaLocation":488},{"text":278,"config":606},{"href":280,"dataGaName":281,"dataGaLocation":488},{"text":283,"config":608},{"href":285,"dataGaName":286,"dataGaLocation":488},{"text":288,"config":610},{"href":290,"dataGaName":291,"dataGaLocation":488},{"text":293,"config":612},{"href":295,"dataGaName":296,"dataGaLocation":488},{"title":311,"links":614},[615,617,619,621,623,625,627,631,636,638,640,642],{"text":318,"config":616},{"href":320,"dataGaName":313,"dataGaLocation":488},{"text":323,"config":618},{"href":325,"dataGaName":326,"dataGaLocation":488},{"text":331,"config":620},{"href":333,"dataGaName":334,"dataGaLocation":488},{"text":336,"config":622},{"href":338,"dataGaName":339,"dataGaLocation":488},{"text":341,"config":624},{"href":343,"dataGaName":344,"dataGaLocation":488},{"text":346,"config":626},{"href":348,"dataGaName":349,"dataGaLocation":488},{"text":628,"config":629},"Sustainability",{"href":630,"dataGaName":628,"dataGaLocation":488},"/sustainability/",{"text":632,"config":633},"Diversity, inclusion and belonging (DIB)",{"href":634,"dataGaName":635,"dataGaLocation":488},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":351,"config":637},{"href":353,"dataGaName":354,"dataGaLocation":488},{"text":361,"config":639},{"href":363,"dataGaName":364,"dataGaLocation":488},{"text":366,"config":641},{"href":368,"dataGaName":369,"dataGaLocation":488},{"text":643,"config":644},"Modern Slavery Transparency Statement",{"href":645,"dataGaName":646,"dataGaLocation":488},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":648,"links":649},"Contact Us",[650,653,655,657,662,667,672],{"text":651,"config":652},"Contact an expert",{"href":50,"dataGaName":51,"dataGaLocation":488},{"text":380,"config":654},{"href":382,"dataGaName":383,"dataGaLocation":488},{"text":385,"config":656},{"href":387,"dataGaName":388,"dataGaLocation":488},{"text":658,"config":659},"Status",{"href":660,"dataGaName":661,"dataGaLocation":488},"https://status.gitlab.com/","status",{"text":663,"config":664},"Terms of use",{"href":665,"dataGaName":666,"dataGaLocation":488},"/terms/","terms of use",{"text":668,"config":669},"Privacy statement",{"href":670,"dataGaName":671,"dataGaLocation":488},"/privacy/","privacy statement",{"text":673,"config":674},"Cookie preferences",{"dataGaName":675,"dataGaLocation":488,"id":676,"isOneTrustButton":104},"cookie preferences","ot-sdk-btn",{"items":678},[679,681,683],{"text":663,"config":680},{"href":665,"dataGaName":666,"dataGaLocation":488},{"text":668,"config":682},{"href":670,"dataGaName":671,"dataGaLocation":488},{"text":673,"config":684},{"dataGaName":675,"dataGaLocation":488,"id":676,"isOneTrustButton":104},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[690],{"_path":691,"_dir":692,"_draft":6,"_partial":6,"_locale":7,"content":693,"config":697,"_id":699,"_type":27,"title":19,"_source":29,"_file":700,"_stem":701,"_extension":32},"/en-us/blog/authors/marco-lenzo","authors",{"name":19,"config":694},{"headshot":695,"ctfId":696},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659488/Blog/Author%20Headshots/gitlab-logo-extra-whitespace.png","Marco-Lenzo",{"template":698},"BlogAuthor","content:en-us:blog:authors:marco-lenzo.yml","en-us/blog/authors/marco-lenzo.yml","en-us/blog/authors/marco-lenzo",{"_path":703,"_dir":35,"_draft":6,"_partial":6,"_locale":7,"header":704,"eyebrow":705,"blurb":706,"button":707,"secondaryButton":711,"_id":713,"_type":27,"title":714,"_source":29,"_file":715,"_stem":716,"_extension":32},"/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":43,"config":708},{"href":709,"dataGaName":46,"dataGaLocation":710},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":48,"config":712},{"href":50,"dataGaName":51,"dataGaLocation":710},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326224138]