[{"data":1,"prerenderedAt":1914},["ShallowReactive",2],{"/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management":3,"navigation-en-us":1127,"banner-en-us":1555,"footer-en-us":1565,"blog-post-authors-en-us-Mario de la Ossa":1809,"blog-related-posts-en-us-using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management":1824,"blog-promotions-en-us":1850,"next-steps-en-us":1904},{"id":4,"title":5,"authors":6,"body":8,"category":1108,"date":1109,"description":1110,"extension":1111,"externalUrl":1112,"faq":1112,"featured":1113,"heroImage":1114,"meta":1115,"navigation":952,"path":1116,"seo":1117,"slug":1121,"stem":1122,"tags":1123,"template":1125,"updatedDate":1112,"__hash__":1126},"blogPosts/en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management.md","How to simplify your smart home configuration with GitLab CI/CD",[7],"Mario de la Ossa",{"type":9,"value":10,"toc":1098},"minimark",[11,27,36,49,75,78,93,98,101,109,113,227,230,234,242,253,264,271,274,285,291,331,336,345,351,399,416,419,450,453,460,467,473,477,480,483,498,503,512,562,565,585,588,602,606,620,732,739,772,778,800,809,812,842,845,849,852,855,871,887,893,1014,1043,1049,1052,1058,1062,1065,1081,1094],[12,13,14,15,21,22,26],"p",{},"So you've read all about the ",[16,17,20],"a",{"href":18,"rel":19},"https://en.wikipedia.org/wiki/Internet_of_things",[],"Internet of Things"," and all the cool stuff you can do with it – from setting up timers for your lights to ",[16,23,25],{"href":24},"/blog/introducing-auto-breakfast-from-gitlab/","making your breakfast"," – and now you're itching to get started? Great!",[12,28,29,30,35],{},"If you're a power user, you've probably settled on using ",[16,31,34],{"href":32,"rel":33},"https://www.home-assistant.io/",[],"Home Assistant"," as your smart home hub, but this choice has a few pitfalls:",[37,38,39,43,46],"ul",{},[40,41,42],"li",{},"It's annoying to SSH into the server itself to change configuration. Wouldn't you like to use your favorite local editor instead?",[40,44,45],{},"How do you keep your configuration backed up?",[40,47,48],{},"How do you protect yourself from accidentally messing up the configuration?",[12,50,51,52,57,58,62,63,68,69,74],{},"In this guide we'll show you how to fix these annoyances yourself, thanks to Git and the power of ",[16,53,56],{"href":54,"rel":55},"https://docs.gitlab.com/ci/pipelines/",[],"GitLab Pipelines","! We will set up a pipeline that will check your home-assistant configuration and deploy it to your home-assistant install, giving you the power to deploy changes from anywhere in the world with a simple ",[59,60,61],"code",{},"git push","!\nDid you go on vacation and forget you wanted your lights to ",[16,64,67],{"href":65,"rel":66},"https://community.home-assistant.io/t/set-random-time-for-random-automatic-turning-off-lights-mimic-someone-is-home/3524",[],"turn on and off randomly to make it seem like someone's home","? No worries! Just open GitLab's ",[16,70,73],{"href":71,"rel":72},"https://docs.gitlab.com/user/project/web_ide/",[],"Web IDE"," and make your changes from your hotel room.",[12,76,77],{},"By the end of this tutorial you'll have:",[37,79,80,87,90],{},[40,81,82,83,86],{},"Automatic configuration backups thanks to ",[59,84,85],{},"git",". You'll be able to see the history of every change you've made and revert changes easily.",[40,88,89],{},"Automatic configuration testing via GitLab pipelines. Never again will a simple typo have you scratching your head, wondering why things don't work!",[40,91,92],{},"An easy way to push changes to your Home Assistant configuration without having to SSH into the server.",[94,95,97],"h2",{"id":96},"requirements","Requirements",[12,99,100],{},"In this guide we'll be assuming a few things:",[37,102,103,106],{},[40,104,105],{},"You installed Home Assistant using the Docker image",[40,107,108],{},"The server Home Assistant runs in is accessible from the internet via SSH (or you're using a self-managed GitLab installation in the same network)",[94,110,112],{"id":111},"set-up-your-server","Set up your server",[114,115,116,119,136,184],"ol",{},[40,117,118],{},"Navigate to your Home Assistant configuration folder.",[40,120,121,122,125,126],{},"Create a new file called ",[59,123,124],{},".gitignore"," with the following content:",[127,128,134],"pre",{"className":129,"code":131,"language":132,"meta":133},[130],"language-text","*.db\n*.log\n","text","",[59,135,131],{"__ignoreMap":133},[40,137,138,139],{},"Initialize the Git repo",[127,140,144],{"className":141,"code":142,"language":143,"meta":133,"style":133},"language-bash shiki shiki-themes github-light","git init\ngit add .\ngit commit -m 'Initial commit'\n","bash",[59,145,146,158,169],{"__ignoreMap":133},[147,148,151,154],"span",{"class":149,"line":150},"line",1,[147,152,85],{"class":153},"s7eDp",[147,155,157],{"class":156},"sYBdl"," init\n",[147,159,161,163,166],{"class":149,"line":160},2,[147,162,85],{"class":153},[147,164,165],{"class":156}," add",[147,167,168],{"class":156}," .\n",[147,170,172,174,177,181],{"class":149,"line":171},3,[147,173,85],{"class":153},[147,175,176],{"class":156}," commit",[147,178,180],{"class":179},"sYu0t"," -m",[147,182,183],{"class":156}," 'Initial commit'\n",[40,185,186,191,192],{},[16,187,190],{"href":188,"rel":189},"https://gitlab.com/projects/new",[],"Create a new GitLab project"," and push to it",[127,193,195],{"className":141,"code":194,"language":143,"meta":133,"style":133},"git remote add origin YOUR_PROJECT_HERE\ngit push -u origin master\n",[59,196,197,212],{"__ignoreMap":133},[147,198,199,201,204,206,209],{"class":149,"line":150},[147,200,85],{"class":153},[147,202,203],{"class":156}," remote",[147,205,165],{"class":156},[147,207,208],{"class":156}," origin",[147,210,211],{"class":156}," YOUR_PROJECT_HERE\n",[147,213,214,216,219,222,224],{"class":149,"line":160},[147,215,85],{"class":153},[147,217,218],{"class":156}," push",[147,220,221],{"class":179}," -u",[147,223,208],{"class":156},[147,225,226],{"class":156}," master\n",[12,228,229],{},"With this you now have a backup of your Home Assistant configuration. Let's now set up the GitLab pipeline!",[94,231,233],{"id":232},"setting-up-the-pipeline","Setting up the pipeline",[12,235,236,237,241],{},"We have a few goals for the ",[16,238,240],{"href":239},"/topics/ci-cd/","CI/CD pipeline",":",[37,243,244,247,250],{},[40,245,246],{},"Test the new configuration to ensure it's valid",[40,248,249],{},"Deploy the new configuration to the Home Assistant server",[40,251,252],{},"Bonus: Notify us of a successful deployment, since the default is to only notify for failures",[12,254,255],{},[16,256,259,260,263],{"href":257,"rel":258},"https://gitlab.com/mdelaossa/hass-via-cicd/blob/master/.gitlab-ci.yml",[],"The complete ",[59,261,262],{},".gitlab-ci.yml"," can be found here.",[12,265,266],{},[16,267,270],{"href":268,"rel":269},"https://docs.gitlab.com/ci/yaml/",[],"General documentation for how to configure jobs can be found here.",[12,272,273],{},"We will be using the following stages in our pipeline:",[37,275,276,279,282],{},[40,277,278],{},"test: Will test the Home Assistant configuration to ensure it is valid",[40,280,281],{},"deploy: Will update the Home Assistant configuration in the server and restart Home Assistant",[40,283,284],{},"notify: Will send a push notification with success/failure state",[12,286,287,288,290],{},"Since these aren't default pipeline stages we need to declare them in our ",[59,289,262],{}," like so:",[127,292,296],{"className":293,"code":294,"language":295,"meta":133,"style":133},"language-yaml shiki shiki-themes github-light","stages:\n  - test\n  - deploy\n  - notify\n\n","yaml",[59,297,298,308,316,323],{"__ignoreMap":133},[147,299,300,304],{"class":149,"line":150},[147,301,303],{"class":302},"shJU0","stages",[147,305,307],{"class":306},"sgsFI",":\n",[147,309,310,313],{"class":149,"line":160},[147,311,312],{"class":306},"  - ",[147,314,315],{"class":156},"test\n",[147,317,318,320],{"class":149,"line":171},[147,319,312],{"class":306},[147,321,322],{"class":156},"deploy\n",[147,324,326,328],{"class":149,"line":325},4,[147,327,312],{"class":306},[147,329,330],{"class":156},"notify\n",[332,333,335],"h3",{"id":334},"automating-configuration-testing","Automating configuration testing",[12,337,338,339,344],{},"Since GitLab CI/CD ",[16,340,343],{"href":341,"rel":342},"https://docs.gitlab.com/ci/docker/using_docker_images/",[],"supports Docker images"," and Home Assistant is available as a Docker image, this is a fairly straightforward stage to add.",[12,346,347,348,350],{},"Add this to your ",[59,349,262],{}," file:",[127,352,354],{"className":293,"code":353,"language":295,"meta":133,"style":133},"test:\n  stage: test\n  image: homeassistant/amd64-homeassistant\n  script:\n    - hass --script check_config -c .\n\n",[59,355,356,363,373,383,390],{"__ignoreMap":133},[147,357,358,361],{"class":149,"line":150},[147,359,360],{"class":302},"test",[147,362,307],{"class":306},[147,364,365,368,371],{"class":149,"line":160},[147,366,367],{"class":302},"  stage",[147,369,370],{"class":306},": ",[147,372,315],{"class":156},[147,374,375,378,380],{"class":149,"line":171},[147,376,377],{"class":302},"  image",[147,379,370],{"class":306},[147,381,382],{"class":156},"homeassistant/amd64-homeassistant\n",[147,384,385,388],{"class":149,"line":325},[147,386,387],{"class":302},"  script",[147,389,307],{"class":306},[147,391,393,396],{"class":149,"line":392},5,[147,394,395],{"class":306},"    - ",[147,397,398],{"class":156},"hass --script check_config -c .\n",[12,400,401,402,404,405,407,408,411,412,415],{},"With this we are creating a job called ",[59,403,360],{}," which will run in the ",[59,406,360],{}," stage. We're using the ",[59,409,410],{},"homeassistant/amd64-homeassistant"," image because it exposes the ",[59,413,414],{},"hass"," command globally so we can use the built-in configuration checking command on our committed files. That's it!",[12,417,418],{},"Feel free to commit and push this change to test it out!",[127,420,422],{"className":141,"code":421,"language":143,"meta":133,"style":133},"git add .\ngit commit -m 'Added testing stage to GitLab pipeline'\ngit push\n",[59,423,424,432,443],{"__ignoreMap":133},[147,425,426,428,430],{"class":149,"line":150},[147,427,85],{"class":153},[147,429,165],{"class":156},[147,431,168],{"class":156},[147,433,434,436,438,440],{"class":149,"line":160},[147,435,85],{"class":153},[147,437,176],{"class":156},[147,439,180],{"class":179},[147,441,442],{"class":156}," 'Added testing stage to GitLab pipeline'\n",[147,444,445,447],{"class":149,"line":171},[147,446,85],{"class":153},[147,448,449],{"class":156}," push\n",[12,451,452],{},"You'll now see that a pipeline gets created whenever you push:",[12,454,455],{},[456,457],"img",{"alt":458,"src":459},"HASS Test pipeline success","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398467/blog/Content%20Images/hass-cicd/pipeline-pass-1.png",[12,461,462,463,466],{},"If your configuration contains any errors, they'll be shown in the ",[59,464,465],{},"Failed Jobs"," view of the pipeline and you'll get an email notifying you of the failure:",[12,468,469],{},[456,470],{"alt":471,"src":472},"HASS Test pipeline failure","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398467/blog/Content%20Images/hass-cicd/pipeline-fail-1.png",[332,474,476],{"id":475},"automating-deployments","Automating deployments",[12,478,479],{},"Now that we have automated testing, let's add another stage that will deploy our new configuration if the tests pass!",[12,481,482],{},"\"Deploying\" in this case will consist of:",[37,484,485,488,495],{},[40,486,487],{},"SSHing into the server",[40,489,490,491,494],{},"Doing a ",[59,492,493],{},"git pull"," to pull down changes from the repo",[40,496,497],{},"Restart the Home Assistant Docker image",[499,500,502],"h4",{"id":501},"preparing-the-server-and-gitlab-for-ssh-access","Preparing the server (and GitLab) for SSH access",[12,504,505,506,511],{},"Since we will be using SSH we need to prepare our server first. We'll follow these instructions from the GitLab documentation.\nWe will also set some ",[16,507,510],{"href":508,"rel":509},"https://gitlab.com/help/ci/variables/README#variables",[],"CI/CD Variables",".",[114,513,514,545,555],{},[40,515,516,517],{},"Generate a new SSH key pair. It's OK to save them to the current folder as you'll delete them later anyway.",[127,518,520],{"className":141,"code":519,"language":143,"meta":133,"style":133},"ssh-keygen -t rsa -C \"hass-deploy\" -b 4096\n",[59,521,522],{"__ignoreMap":133},[147,523,524,527,530,533,536,539,542],{"class":149,"line":150},[147,525,526],{"class":153},"ssh-keygen",[147,528,529],{"class":179}," -t",[147,531,532],{"class":156}," rsa",[147,534,535],{"class":179}," -C",[147,537,538],{"class":156}," \"hass-deploy\"",[147,540,541],{"class":179}," -b",[147,543,544],{"class":179}," 4096\n",[40,546,547,548,551,552],{},"On the server that runs Home Assistant, save the contents of the public key (the file ending in ",[59,549,550],{},".pub",") to ",[59,553,554],{},"/home/user_running_hass/.ssh/authorized_keys",[40,556,557,558,561],{},"Go to your GitLab project's CI/CD variables (inside Settings). Add the contents of the private key file to a variable named ",[59,559,560],{},"SSH_PRIVATE_KEY",". You can now delete the SSH key pair files if you'd like, or store them somewhere safe.",[12,563,564],{},"We also need to add our server's host keys to the GitLab runner so the runner will be able to SSH successfully. Alternatively we could disable host key checking, but this is not recommended.",[114,566,567,574],{},[40,568,569,570,573],{},"On your server, run ",[59,571,572],{},"ssh-keyscan example.com"," where example.com is the domain or IP of your server.",[40,575,576,577,580,581,584],{},"Create a new CI/CD variable called ",[59,578,579],{},"SSH_KNOWN_HOSTS"," and add the output of ",[59,582,583],{},"ssh-keyscan"," to it.",[12,586,587],{},"You should also create two other CI/CD variables (optional):",[37,589,590,596],{},[40,591,592,595],{},[59,593,594],{},"DEPLOY_USER",": the user running HASS that the runner with SSH into the server as to perform the deploy",[40,597,598,601],{},[59,599,600],{},"DEPLOY_HOST",": the domain or IP of the server",[499,603,605],{"id":604},"the-deploy-stage","The deploy stage",[12,607,608,609,611,612,615,616,619],{},"Now that we have prepared our server and GitLab CI/CD variables, we can add our deploy stage to ",[59,610,262],{},". Please note that we are using the ",[59,613,614],{},"only: "," keyword so that only new commits in the ",[59,617,618],{},"master"," branch will attempt a deploy.",[127,621,623],{"className":293,"code":622,"language":295,"meta":133,"style":133},"deploy:\n  stage: deploy\n  only:\n    - master\n  before_script:\n    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n    - eval $(ssh-agent -s)\n    - echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add - > /dev/null\n    - mkdir -p ~/.ssh\n    - chmod 700 ~/.ssh\n    - echo \"$SSH_KNOWN_HOSTS\" > ~/.ssh/known_hosts\n    - chmod 644 ~/.ssh/known_hosts\n  script:\n    - ssh $DEPLOY_USER@$DEPLOY_HOST \"cd '$DEPLOY_PATH'; git pull; docker restart home-assistant\"\n\n",[59,624,625,632,640,647,654,661,669,677,685,693,701,709,717,724],{"__ignoreMap":133},[147,626,627,630],{"class":149,"line":150},[147,628,629],{"class":302},"deploy",[147,631,307],{"class":306},[147,633,634,636,638],{"class":149,"line":160},[147,635,367],{"class":302},[147,637,370],{"class":306},[147,639,322],{"class":156},[147,641,642,645],{"class":149,"line":171},[147,643,644],{"class":302},"  only",[147,646,307],{"class":306},[147,648,649,651],{"class":149,"line":325},[147,650,395],{"class":306},[147,652,653],{"class":156},"master\n",[147,655,656,659],{"class":149,"line":392},[147,657,658],{"class":302},"  before_script",[147,660,307],{"class":306},[147,662,664,666],{"class":149,"line":663},6,[147,665,395],{"class":306},[147,667,668],{"class":156},"'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'\n",[147,670,672,674],{"class":149,"line":671},7,[147,673,395],{"class":306},[147,675,676],{"class":156},"eval $(ssh-agent -s)\n",[147,678,680,682],{"class":149,"line":679},8,[147,681,395],{"class":306},[147,683,684],{"class":156},"echo \"$SSH_PRIVATE_KEY\" | tr -d '\\r' | ssh-add - > /dev/null\n",[147,686,688,690],{"class":149,"line":687},9,[147,689,395],{"class":306},[147,691,692],{"class":156},"mkdir -p ~/.ssh\n",[147,694,696,698],{"class":149,"line":695},10,[147,697,395],{"class":306},[147,699,700],{"class":156},"chmod 700 ~/.ssh\n",[147,702,704,706],{"class":149,"line":703},11,[147,705,395],{"class":306},[147,707,708],{"class":156},"echo \"$SSH_KNOWN_HOSTS\" > ~/.ssh/known_hosts\n",[147,710,712,714],{"class":149,"line":711},12,[147,713,395],{"class":306},[147,715,716],{"class":156},"chmod 644 ~/.ssh/known_hosts\n",[147,718,720,722],{"class":149,"line":719},13,[147,721,387],{"class":302},[147,723,307],{"class":306},[147,725,727,729],{"class":149,"line":726},14,[147,728,395],{"class":306},[147,730,731],{"class":156},"ssh $DEPLOY_USER@$DEPLOY_HOST \"cd '$DEPLOY_PATH'; git pull; docker restart home-assistant\"\n",[12,733,734,735,738],{},"The ",[59,736,737],{},"before_script"," above is in charge of:",[37,740,741,748,753,759,766],{},[40,742,743,744,747],{},"Making sure ",[59,745,746],{},"ssh-agent"," is installed and installing it otherwise",[40,749,743,750,752],{},[59,751,746],{}," is running",[40,754,755,756,758],{},"Adding the ",[59,757,560],{}," to the keys to use when logging into a server",[40,760,761,762,765],{},"Creating the ",[59,763,764],{},".ssh"," folder with required permissions",[40,767,768,769,771],{},"Adding the values we added to the ",[59,770,579],{}," variable to the proper location",[12,773,734,774,777],{},[59,775,776],{},"script"," portion is what actually deploys our new configuration:",[37,779,780,787,793],{},[40,781,782,783,786],{},"We ",[59,784,785],{},"cd"," into the proper location (where the Home Assistant configuration files are kept)",[40,788,789,790,792],{},"We update the configuration with a ",[59,791,493],{},", since this directory is a Git repo",[40,794,795,796,799],{},"We restart Home Assistant (in this case the Docker image was created with the name ",[59,797,798],{},"home-assistant",". Please use the name of your container)",[12,801,802,803,805,806,808],{},"Note: If you did not create ",[59,804,594],{}," and ",[59,807,600],{}," variables on GitLab, please replace the proper values in the script",[12,810,811],{},"Now let's commit and push this new stage to GitLab!",[127,813,815],{"className":141,"code":814,"language":143,"meta":133,"style":133},"git add .\ngit commit -m 'Added deploy stage to GitLab pipeline'\ngit push\n",[59,816,817,825,836],{"__ignoreMap":133},[147,818,819,821,823],{"class":149,"line":150},[147,820,85],{"class":153},[147,822,165],{"class":156},[147,824,168],{"class":156},[147,826,827,829,831,833],{"class":149,"line":160},[147,828,85],{"class":153},[147,830,176],{"class":156},[147,832,180],{"class":179},[147,834,835],{"class":156}," 'Added deploy stage to GitLab pipeline'\n",[147,837,838,840],{"class":149,"line":171},[147,839,85],{"class":153},[147,841,449],{"class":156},[12,843,844],{},"With this new stage added, you can now edit your configuration from anywhere (including the GitLab Web IDE!) and be confident that these changes will be pushed to your Home Assistant server if there are no issues with the configuration.\nThere's no longer a need to figure out how to connect directly to your Home Assistant server to make the edits you need.",[332,846,848],{"id":847},"bonus-successful-deployment-notifications","Bonus: Successful deployment notifications",[12,850,851],{},"You'll notice that if the configuration is wrong or an error occurs during the deployment, you will get an email notification, but what about when everything runs successfully?",[12,853,854],{},"We have two options:",[114,856,857,864],{},[40,858,859,860,863],{},"Enable the ",[59,861,862],{},"Pipeline Emails"," integration and set it to notify on every pipeline",[40,865,866,867,870],{},"Add a new stage called ",[59,868,869],{},"notify"," and use it to send push notifications to your phone",[12,872,873,874,879,880,883,884,511],{},"While email is really nice, there's something really satisfying about getting push notification for your services, so let's set things up using ",[16,875,878],{"href":876,"rel":877},"https://pushover.net/",[],"Pushover",".\nYou'll need to create an 'Application' and add the token you get to a GitLab variable called ",[59,881,882],{},"PUSHOVER_API_TOKEN",". You'll also need to add your user key to a variable called ",[59,885,886],{},"PUSHOVER_USER_TOKEN",[12,888,889,890,892],{},"Since we'd like a different notification depending on whether our pipeline passed or failed, we will be adding two jobs to the ",[59,891,869],{}," stage:",[127,894,896],{"className":293,"code":895,"language":295,"meta":133,"style":133},"notify_success:\n  stage: notify\n  allow_failure: true\n  only:\n    - master\n  script:\n    - curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config deployed successfully!\" https://api.pushover.net/1/messages.json\n\nnotify_fail:\n  stage: notify\n  allow_failure: true\n  only:\n    - master\n  when: on_failure\n  script:\n    - curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config failed. Please check for errors\" https://api.pushover.net/1/messages.json\n\n",[59,897,898,905,913,923,929,935,941,948,954,961,969,977,983,989,999,1006],{"__ignoreMap":133},[147,899,900,903],{"class":149,"line":150},[147,901,902],{"class":302},"notify_success",[147,904,307],{"class":306},[147,906,907,909,911],{"class":149,"line":160},[147,908,367],{"class":302},[147,910,370],{"class":306},[147,912,330],{"class":156},[147,914,915,918,920],{"class":149,"line":171},[147,916,917],{"class":302},"  allow_failure",[147,919,370],{"class":306},[147,921,922],{"class":179},"true\n",[147,924,925,927],{"class":149,"line":325},[147,926,644],{"class":302},[147,928,307],{"class":306},[147,930,931,933],{"class":149,"line":392},[147,932,395],{"class":306},[147,934,653],{"class":156},[147,936,937,939],{"class":149,"line":663},[147,938,387],{"class":302},[147,940,307],{"class":306},[147,942,943,945],{"class":149,"line":671},[147,944,395],{"class":306},[147,946,947],{"class":156},"curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config deployed successfully!\" https://api.pushover.net/1/messages.json\n",[147,949,950],{"class":149,"line":679},[147,951,953],{"emptyLinePlaceholder":952},true,"\n",[147,955,956,959],{"class":149,"line":687},[147,957,958],{"class":302},"notify_fail",[147,960,307],{"class":306},[147,962,963,965,967],{"class":149,"line":695},[147,964,367],{"class":302},[147,966,370],{"class":306},[147,968,330],{"class":156},[147,970,971,973,975],{"class":149,"line":703},[147,972,917],{"class":302},[147,974,370],{"class":306},[147,976,922],{"class":179},[147,978,979,981],{"class":149,"line":711},[147,980,644],{"class":302},[147,982,307],{"class":306},[147,984,985,987],{"class":149,"line":719},[147,986,395],{"class":306},[147,988,653],{"class":156},[147,990,991,994,996],{"class":149,"line":726},[147,992,993],{"class":302},"  when",[147,995,370],{"class":306},[147,997,998],{"class":156},"on_failure\n",[147,1000,1002,1004],{"class":149,"line":1001},15,[147,1003,387],{"class":302},[147,1005,307],{"class":306},[147,1007,1009,1011],{"class":149,"line":1008},16,[147,1010,395],{"class":306},[147,1012,1013],{"class":156},"curl -s --form-string \"token=$PUSHOVER_API_TOKEN\" --form-string \"user=$PUSHOVER_USER_TOKEN\" --form-string \"message=New Hass config failed. Please check for errors\" https://api.pushover.net/1/messages.json\n",[12,1015,1016,1017,1019,1020,1022,1023,1025,1026,1029,1030,1034,1035,1038,1039,1042],{},"Our first job, ",[59,1018,902],{},", runs when the stage before it (",[59,1021,629],{},") completes successfully. This is the default for GitLab. Our ",[59,1024,958],{}," job on the other hand has ",[59,1027,1028],{},"when: on_failure"," set, which means it will ",[1031,1032,1033],"em",{},"only"," run when the stage before it fails. We also set ",[59,1036,1037],{},"allow_failure: true"," on both these jobs so that we aren't notified of a failed pipeline if for some reason the notification commands fail. We also set the ",[59,1040,1041],{},"only: - master"," option since deploys only happen on the master branch.",[12,1044,1045,1046,1048],{},"We are using Pushover's API to send the message we want in the ",[59,1047,776],{}," area.",[12,1050,1051],{},"With this final stage in place, your pipeline should now look like this:",[12,1053,1054],{},[456,1055],{"alt":1056,"src":1057},"HASS pipeline overview","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398467/blog/Content%20Images/hass-cicd/pipeline-final-1.png",[332,1059,1061],{"id":1060},"enjoy","Enjoy!",[12,1063,1064],{},"There you have it! Now you can edit your Home Assistant configuration from anywhere you'd like, using your favorite editor, by following three simple steps:",[114,1066,1067,1073,1076],{},[40,1068,1069,1072],{},[59,1070,1071],{},"git clone PATH_TO_REPO"," (if you have not cloned it before)",[40,1074,1075],{},"Edit the configuration",[40,1077,1078],{},[59,1079,1080],{},"git push -u remote master",[12,1082,1083,1088,1089],{},[16,1084,1087],{"href":1085,"rel":1086},"https://unsplash.com/photos/9TF54VdG0ws?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText",[],"Photo"," by Kevin Bhagat on ",[16,1090,1093],{"href":1091,"rel":1092},"https://unsplash.com/search/photos/smart-home?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText",[],"Unsplash",[1095,1096,1097],"style",{},"html pre.shiki code .s7eDp, html code.shiki .s7eDp{--shiki-default:#6F42C1}html pre.shiki code .sYBdl, html code.shiki .sYBdl{--shiki-default:#032F62}html pre.shiki code .sYu0t, html code.shiki .sYu0t{--shiki-default:#005CC5}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shJU0, html code.shiki .shJU0{--shiki-default:#22863A}html pre.shiki code .sgsFI, html code.shiki .sgsFI{--shiki-default:#24292E}",{"title":133,"searchDepth":160,"depth":160,"links":1099},[1100,1101,1102],{"id":96,"depth":160,"text":97},{"id":111,"depth":160,"text":112},{"id":232,"depth":160,"text":233,"children":1103},[1104,1105,1106,1107],{"id":334,"depth":171,"text":335},{"id":475,"depth":171,"text":476},{"id":847,"depth":171,"text":848},{"id":1060,"depth":171,"text":1061},"engineering","2018-08-02","How to use GitLab pipelines to automatically test and deploy new home-assistant configurations, wherever you are.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749678717/Blog/Hero%20Images/ci-smart-home-configuration.jpg",{},"/en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management",{"title":5,"description":1110,"ogTitle":5,"ogDescription":1110,"noIndex":1113,"ogImage":1114,"ogUrl":1118,"ogSiteName":1119,"ogType":1120,"canonicalUrls":1118},"https://about.gitlab.com/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management","https://about.gitlab.com","article","using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management","en-us/blog/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management",[1124,85],"CI","BlogPost","siKxVq8enkBiV4iDTlYltjuS0qXOytpUWsa9mMQDgFE",{"logo":1128,"freeTrial":1133,"sales":1138,"login":1143,"items":1148,"search":1475,"minimal":1506,"duo":1525,"switchNav":1534,"pricingDeployment":1545},{"config":1129},{"href":1130,"dataGaName":1131,"dataGaLocation":1132},"/","gitlab logo","header",{"text":1134,"config":1135},"Get free trial",{"href":1136,"dataGaName":1137,"dataGaLocation":1132},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1139,"config":1140},"Request a demo",{"href":1141,"dataGaName":1142,"dataGaLocation":1132},"/sales/?contact-topic=request-demo","sales",{"text":1144,"config":1145},"Sign in",{"href":1146,"dataGaName":1147,"dataGaLocation":1132},"https://gitlab.com/users/sign_in/","sign in",[1149,1178,1278,1283,1397,1453],{"text":1150,"config":1151,"menu":1153},"Platform",{"dataNavLevelOne":1152},"platform",{"type":1154,"columns":1155},"cards",[1156,1162,1170],{"title":1150,"description":1157,"link":1158},"The intelligent orchestration platform for DevSecOps",{"text":1159,"config":1160},"Explore our Platform",{"href":1161,"dataGaName":1152,"dataGaLocation":1132},"/platform/",{"title":1163,"description":1164,"link":1165},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1166,"config":1167},"Meet GitLab Duo",{"href":1168,"dataGaName":1169,"dataGaLocation":1132},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1171,"description":1172,"link":1173},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1174,"config":1175},"Learn more",{"href":1176,"dataGaName":1177,"dataGaLocation":1132},"/why-gitlab/","why gitlab",{"text":1179,"left":952,"config":1180,"menu":1182},"Product",{"dataNavLevelOne":1181},"solutions",{"type":1183,"link":1184,"columns":1188,"feature":1257},"lists",{"text":1185,"config":1186},"View all Solutions",{"href":1187,"dataGaName":1181,"dataGaLocation":1132},"/solutions/",[1189,1213,1236],{"title":1190,"description":1191,"link":1192,"items":1197},"Automation","CI/CD and automation to accelerate deployment",{"config":1193},{"icon":1194,"href":1195,"dataGaName":1196,"dataGaLocation":1132},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1198,1202,1205,1209],{"text":1199,"config":1200},"CI/CD",{"href":1201,"dataGaLocation":1132,"dataGaName":1199},"/solutions/continuous-integration/",{"text":1163,"config":1203},{"href":1168,"dataGaLocation":1132,"dataGaName":1204},"gitlab duo agent platform - product menu",{"text":1206,"config":1207},"Source Code Management",{"href":1208,"dataGaLocation":1132,"dataGaName":1206},"/solutions/source-code-management/",{"text":1210,"config":1211},"Automated Software Delivery",{"href":1195,"dataGaLocation":1132,"dataGaName":1212},"Automated software delivery",{"title":1214,"description":1215,"link":1216,"items":1221},"Security","Deliver code faster without compromising security",{"config":1217},{"href":1218,"dataGaName":1219,"dataGaLocation":1132,"icon":1220},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1222,1226,1231],{"text":1223,"config":1224},"Application Security Testing",{"href":1218,"dataGaName":1225,"dataGaLocation":1132},"Application security testing",{"text":1227,"config":1228},"Software Supply Chain Security",{"href":1229,"dataGaLocation":1132,"dataGaName":1230},"/solutions/supply-chain/","Software supply chain security",{"text":1232,"config":1233},"Software Compliance",{"href":1234,"dataGaName":1235,"dataGaLocation":1132},"/solutions/software-compliance/","software compliance",{"title":1237,"link":1238,"items":1243},"Measurement",{"config":1239},{"icon":1240,"href":1241,"dataGaName":1242,"dataGaLocation":1132},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1244,1248,1252],{"text":1245,"config":1246},"Visibility & Measurement",{"href":1241,"dataGaLocation":1132,"dataGaName":1247},"Visibility and Measurement",{"text":1249,"config":1250},"Value Stream Management",{"href":1251,"dataGaLocation":1132,"dataGaName":1249},"/solutions/value-stream-management/",{"text":1253,"config":1254},"Analytics & Insights",{"href":1255,"dataGaLocation":1132,"dataGaName":1256},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1258,"type":1183,"items":1259},"GitLab for",[1260,1266,1272],{"text":1261,"config":1262},"Enterprise",{"icon":1263,"href":1264,"dataGaLocation":1132,"dataGaName":1265},"Building","/enterprise/","enterprise",{"text":1267,"config":1268},"Small Business",{"icon":1269,"href":1270,"dataGaLocation":1132,"dataGaName":1271},"Work","/small-business/","small business",{"text":1273,"config":1274},"Public Sector",{"icon":1275,"href":1276,"dataGaLocation":1132,"dataGaName":1277},"Organization","/solutions/public-sector/","public sector",{"text":1279,"config":1280},"Pricing",{"href":1281,"dataGaName":1282,"dataGaLocation":1132,"dataNavLevelOne":1282},"/pricing/","pricing",{"text":1284,"config":1285,"menu":1287},"Resources",{"dataNavLevelOne":1286},"resources",{"type":1183,"link":1288,"columns":1292,"feature":1386},{"text":1289,"config":1290},"View all resources",{"href":1291,"dataGaName":1286,"dataGaLocation":1132},"/resources/",[1293,1326,1353],{"title":1294,"items":1295},"Getting started",[1296,1301,1306,1311,1316,1321],{"text":1297,"config":1298},"Install",{"href":1299,"dataGaName":1300,"dataGaLocation":1132},"/install/","install",{"text":1302,"config":1303},"Quick start guides",{"href":1304,"dataGaName":1305,"dataGaLocation":1132},"/get-started/","quick setup checklists",{"text":1307,"config":1308},"Learn",{"href":1309,"dataGaLocation":1132,"dataGaName":1310},"https://university.gitlab.com/","learn",{"text":1312,"config":1313},"Product documentation",{"href":1314,"dataGaName":1315,"dataGaLocation":1132},"https://docs.gitlab.com/","product documentation",{"text":1317,"config":1318},"Best practice videos",{"href":1319,"dataGaName":1320,"dataGaLocation":1132},"/getting-started-videos/","best practice videos",{"text":1322,"config":1323},"Integrations",{"href":1324,"dataGaName":1325,"dataGaLocation":1132},"/integrations/","integrations",{"title":1327,"items":1328},"Discover",[1329,1334,1339,1344,1348],{"text":1330,"config":1331},"Customer success stories",{"href":1332,"dataGaName":1333,"dataGaLocation":1132},"/customers/","customer success stories",{"text":1335,"config":1336},"Blog",{"href":1337,"dataGaName":1338,"dataGaLocation":1132},"/blog/","blog",{"text":1340,"config":1341},"Demo Hub",{"href":1342,"dataGaName":1343,"dataGaLocation":1132},"/demo-hub/","demo hub",{"text":1345,"config":1346},"The Source",{"href":1347,"dataGaName":1338,"dataGaLocation":1132},"/the-source/",{"text":1349,"config":1350},"Remote",{"href":1351,"dataGaName":1352,"dataGaLocation":1132},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1354,"items":1355},"Connect",[1356,1361,1366,1371,1376,1381],{"text":1357,"config":1358},"GitLab Services",{"href":1359,"dataGaName":1360,"dataGaLocation":1132},"/services/","services",{"text":1362,"config":1363},"Contribute",{"href":1364,"dataGaName":1365,"dataGaLocation":1132},"https://contributors.gitlab.com","contribute",{"text":1367,"config":1368},"Community",{"href":1369,"dataGaName":1370,"dataGaLocation":1132},"/community/","community",{"text":1372,"config":1373},"Forum",{"href":1374,"dataGaName":1375,"dataGaLocation":1132},"https://forum.gitlab.com/","forum",{"text":1377,"config":1378},"Events",{"href":1379,"dataGaName":1380,"dataGaLocation":1132},"/events/","events",{"text":1382,"config":1383},"Partners",{"href":1384,"dataGaName":1385,"dataGaLocation":1132},"/partners/","partners",{"config":1387,"title":1390,"text":1391,"link":1392},{"background":1388,"textColor":1389},"url('https://res.cloudinary.com/about-gitlab-com/image/upload/v1777322348/qpq8yrgn8knii57omj0c.png')","#000","What’s new in GitLab","Stay updated with our latest features and improvements.",{"text":1393,"config":1394},"Read the latest",{"href":1395,"dataGaName":1396,"dataGaLocation":1132},"/whats-new/","whats new",{"text":1398,"config":1399,"menu":1401},"Company",{"dataNavLevelOne":1400},"company",{"type":1183,"columns":1402},[1403],{"items":1404},[1405,1410,1416,1418,1423,1428,1433,1438,1443,1448],{"text":1406,"config":1407},"About",{"href":1408,"dataGaName":1409,"dataGaLocation":1132},"/company/","about",{"text":1411,"config":1412,"footerGa":1415},"Jobs",{"href":1413,"dataGaName":1414,"dataGaLocation":1132},"/jobs/","jobs",{"dataGaName":1414},{"text":1377,"config":1417},{"href":1379,"dataGaName":1380,"dataGaLocation":1132},{"text":1419,"config":1420},"Leadership",{"href":1421,"dataGaName":1422,"dataGaLocation":1132},"/company/team/e-group/","leadership",{"text":1424,"config":1425},"Handbook",{"href":1426,"dataGaName":1427,"dataGaLocation":1132},"https://handbook.gitlab.com/","handbook",{"text":1429,"config":1430},"Investor relations",{"href":1431,"dataGaName":1432,"dataGaLocation":1132},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1434,"config":1435},"Trust Center",{"href":1436,"dataGaName":1437,"dataGaLocation":1132},"/security/","trust center",{"text":1439,"config":1440},"AI Transparency Center",{"href":1441,"dataGaName":1442,"dataGaLocation":1132},"/ai-transparency-center/","ai transparency center",{"text":1444,"config":1445},"Newsletter",{"href":1446,"dataGaName":1447,"dataGaLocation":1132},"/company/contact/#contact-forms","newsletter",{"text":1449,"config":1450},"Press",{"href":1451,"dataGaName":1452,"dataGaLocation":1132},"/press/","press",{"text":1454,"config":1455,"menu":1456},"Contact us",{"dataNavLevelOne":1400},{"type":1183,"columns":1457},[1458],{"items":1459},[1460,1465,1470],{"text":1461,"config":1462},"Talk to sales",{"href":1463,"dataGaName":1464,"dataGaLocation":1132},"/sales/","talk to sales",{"text":1466,"config":1467},"Support portal",{"href":1468,"dataGaName":1469,"dataGaLocation":1132},"https://support.gitlab.com/hc/en-us","support portal",{"text":1471,"config":1472},"Customer portal",{"href":1473,"dataGaName":1474,"dataGaLocation":1132},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":1476,"login":1477,"suggestions":1484},"Close",{"text":1478,"link":1479},"To search repositories and projects, login to",{"text":1480,"config":1481},"gitlab.com",{"href":1146,"dataGaName":1482,"dataGaLocation":1483},"search login","search",{"text":1485,"default":1486},"Suggestions",[1487,1489,1493,1495,1499,1503],{"text":1163,"config":1488},{"href":1168,"dataGaName":1163,"dataGaLocation":1483},{"text":1490,"config":1491},"Code Suggestions (AI)",{"href":1492,"dataGaName":1490,"dataGaLocation":1483},"/solutions/code-suggestions/",{"text":1199,"config":1494},{"href":1201,"dataGaName":1199,"dataGaLocation":1483},{"text":1496,"config":1497},"GitLab on AWS",{"href":1498,"dataGaName":1496,"dataGaLocation":1483},"/partners/technology-partners/aws/",{"text":1500,"config":1501},"GitLab on Google Cloud",{"href":1502,"dataGaName":1500,"dataGaLocation":1483},"/partners/technology-partners/google-cloud-platform/",{"text":1504,"config":1505},"Why GitLab?",{"href":1176,"dataGaName":1504,"dataGaLocation":1483},{"freeTrial":1507,"mobileIcon":1512,"desktopIcon":1517,"secondaryButton":1520},{"text":1508,"config":1509},"Start free trial",{"href":1510,"dataGaName":1137,"dataGaLocation":1511},"https://gitlab.com/-/trials/new/","nav",{"altText":1513,"config":1514},"Gitlab Icon",{"src":1515,"dataGaName":1516,"dataGaLocation":1511},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1513,"config":1518},{"src":1519,"dataGaName":1516,"dataGaLocation":1511},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1521,"config":1522},"Get Started",{"href":1523,"dataGaName":1524,"dataGaLocation":1511},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1526,"mobileIcon":1530,"desktopIcon":1532},{"text":1527,"config":1528},"Learn more about GitLab Duo",{"href":1168,"dataGaName":1529,"dataGaLocation":1511},"gitlab duo",{"altText":1513,"config":1531},{"src":1515,"dataGaName":1516,"dataGaLocation":1511},{"altText":1513,"config":1533},{"src":1519,"dataGaName":1516,"dataGaLocation":1511},{"button":1535,"mobileIcon":1540,"desktopIcon":1542},{"text":1536,"config":1537},"/switch",{"href":1538,"dataGaName":1539,"dataGaLocation":1511},"#contact","switch",{"altText":1513,"config":1541},{"src":1515,"dataGaName":1516,"dataGaLocation":1511},{"altText":1513,"config":1543},{"src":1544,"dataGaName":1516,"dataGaLocation":1511},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1546,"mobileIcon":1551,"desktopIcon":1553},{"text":1547,"config":1548},"Back to pricing",{"href":1281,"dataGaName":1549,"dataGaLocation":1511,"icon":1550},"back to pricing","GoBack",{"altText":1513,"config":1552},{"src":1515,"dataGaName":1516,"dataGaLocation":1511},{"altText":1513,"config":1554},{"src":1519,"dataGaName":1516,"dataGaLocation":1511},{"title":1556,"titleMobile":1557,"button":1558,"config":1563},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1174,"config":1559},{"href":1560,"dataGaName":1561,"dataGaLocation":1562},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1564,"disabled":1113},"release",{"data":1566},{"text":1567,"source":1568,"edit":1574,"contribute":1579,"config":1584,"items":1589,"minimal":1798},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1569,"config":1570},"View page source",{"href":1571,"dataGaName":1572,"dataGaLocation":1573},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1575,"config":1576},"Edit this page",{"href":1577,"dataGaName":1578,"dataGaLocation":1573},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1580,"config":1581},"Please contribute",{"href":1582,"dataGaName":1583,"dataGaLocation":1573},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1585,"facebook":1586,"youtube":1587,"linkedin":1588},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1590,1637,1690,1734,1766],{"title":1279,"links":1591,"subMenu":1606},[1592,1596,1601],{"text":1593,"config":1594},"View plans",{"href":1281,"dataGaName":1595,"dataGaLocation":1573},"view plans",{"text":1597,"config":1598},"Why Premium?",{"href":1599,"dataGaName":1600,"dataGaLocation":1573},"/pricing/premium/","why premium",{"text":1602,"config":1603},"Why Ultimate?",{"href":1604,"dataGaName":1605,"dataGaLocation":1573},"/pricing/ultimate/","why ultimate",[1607],{"title":1608,"links":1609},"Contact Us",[1610,1613,1615,1617,1622,1627,1632],{"text":1611,"config":1612},"Contact sales",{"href":1463,"dataGaName":1142,"dataGaLocation":1573},{"text":1466,"config":1614},{"href":1468,"dataGaName":1469,"dataGaLocation":1573},{"text":1471,"config":1616},{"href":1473,"dataGaName":1474,"dataGaLocation":1573},{"text":1618,"config":1619},"Status",{"href":1620,"dataGaName":1621,"dataGaLocation":1573},"https://status.gitlab.com/","status",{"text":1623,"config":1624},"Terms of use",{"href":1625,"dataGaName":1626,"dataGaLocation":1573},"/terms/","terms of use",{"text":1628,"config":1629},"Privacy statement",{"href":1630,"dataGaName":1631,"dataGaLocation":1573},"/privacy/","privacy statement",{"text":1633,"config":1634},"Cookie preferences",{"dataGaName":1635,"dataGaLocation":1573,"id":1636,"isOneTrustButton":952},"cookie preferences","ot-sdk-btn",{"title":1179,"links":1638,"subMenu":1647},[1639,1643],{"text":1640,"config":1641},"DevSecOps platform",{"href":1161,"dataGaName":1642,"dataGaLocation":1573},"devsecops platform",{"text":1644,"config":1645},"AI-Assisted Development",{"href":1168,"dataGaName":1646,"dataGaLocation":1573},"ai-assisted development",[1648],{"title":1649,"links":1650},"Topics",[1651,1655,1660,1665,1670,1675,1680,1685],{"text":1652,"config":1653},"CICD",{"href":239,"dataGaName":1654,"dataGaLocation":1573},"cicd",{"text":1656,"config":1657},"GitOps",{"href":1658,"dataGaName":1659,"dataGaLocation":1573},"/topics/gitops/","gitops",{"text":1661,"config":1662},"DevOps",{"href":1663,"dataGaName":1664,"dataGaLocation":1573},"/topics/devops/","devops",{"text":1666,"config":1667},"Version Control",{"href":1668,"dataGaName":1669,"dataGaLocation":1573},"/topics/version-control/","version control",{"text":1671,"config":1672},"DevSecOps",{"href":1673,"dataGaName":1674,"dataGaLocation":1573},"/topics/devsecops/","devsecops",{"text":1676,"config":1677},"Cloud Native",{"href":1678,"dataGaName":1679,"dataGaLocation":1573},"/topics/cloud-native/","cloud native",{"text":1681,"config":1682},"AI for Coding",{"href":1683,"dataGaName":1684,"dataGaLocation":1573},"/topics/devops/ai-for-coding/","ai for coding",{"text":1686,"config":1687},"Agentic AI",{"href":1688,"dataGaName":1689,"dataGaLocation":1573},"/topics/agentic-ai/","agentic ai",{"title":1691,"links":1692},"Solutions",[1693,1695,1697,1702,1706,1709,1713,1716,1718,1721,1724,1729],{"text":1223,"config":1694},{"href":1218,"dataGaName":1223,"dataGaLocation":1573},{"text":1212,"config":1696},{"href":1195,"dataGaName":1196,"dataGaLocation":1573},{"text":1698,"config":1699},"Agile development",{"href":1700,"dataGaName":1701,"dataGaLocation":1573},"/solutions/agile-delivery/","agile delivery",{"text":1703,"config":1704},"SCM",{"href":1208,"dataGaName":1705,"dataGaLocation":1573},"source code management",{"text":1652,"config":1707},{"href":1201,"dataGaName":1708,"dataGaLocation":1573},"continuous integration & delivery",{"text":1710,"config":1711},"Value stream management",{"href":1251,"dataGaName":1712,"dataGaLocation":1573},"value stream management",{"text":1656,"config":1714},{"href":1715,"dataGaName":1659,"dataGaLocation":1573},"/solutions/gitops/",{"text":1261,"config":1717},{"href":1264,"dataGaName":1265,"dataGaLocation":1573},{"text":1719,"config":1720},"Small business",{"href":1270,"dataGaName":1271,"dataGaLocation":1573},{"text":1722,"config":1723},"Public sector",{"href":1276,"dataGaName":1277,"dataGaLocation":1573},{"text":1725,"config":1726},"Education",{"href":1727,"dataGaName":1728,"dataGaLocation":1573},"/solutions/education/","education",{"text":1730,"config":1731},"Financial services",{"href":1732,"dataGaName":1733,"dataGaLocation":1573},"/solutions/finance/","financial services",{"title":1284,"links":1735},[1736,1738,1740,1742,1745,1747,1750,1752,1754,1756,1758,1760,1762,1764],{"text":1297,"config":1737},{"href":1299,"dataGaName":1300,"dataGaLocation":1573},{"text":1302,"config":1739},{"href":1304,"dataGaName":1305,"dataGaLocation":1573},{"text":1307,"config":1741},{"href":1309,"dataGaName":1310,"dataGaLocation":1573},{"text":1312,"config":1743},{"href":1314,"dataGaName":1744,"dataGaLocation":1573},"docs",{"text":1335,"config":1746},{"href":1337,"dataGaName":1338,"dataGaLocation":1573},{"text":1748,"config":1749},"What's new",{"href":1395,"dataGaName":1396,"dataGaLocation":1573},{"text":1330,"config":1751},{"href":1332,"dataGaName":1333,"dataGaLocation":1573},{"text":1349,"config":1753},{"href":1351,"dataGaName":1352,"dataGaLocation":1573},{"text":1357,"config":1755},{"href":1359,"dataGaName":1360,"dataGaLocation":1573},{"text":1362,"config":1757},{"href":1364,"dataGaName":1365,"dataGaLocation":1573},{"text":1367,"config":1759},{"href":1369,"dataGaName":1370,"dataGaLocation":1573},{"text":1372,"config":1761},{"href":1374,"dataGaName":1375,"dataGaLocation":1573},{"text":1377,"config":1763},{"href":1379,"dataGaName":1380,"dataGaLocation":1573},{"text":1382,"config":1765},{"href":1384,"dataGaName":1385,"dataGaLocation":1573},{"title":1398,"links":1767},[1768,1770,1772,1774,1776,1778,1782,1787,1789,1791,1793],{"text":1406,"config":1769},{"href":1408,"dataGaName":1400,"dataGaLocation":1573},{"text":1411,"config":1771},{"href":1413,"dataGaName":1414,"dataGaLocation":1573},{"text":1419,"config":1773},{"href":1421,"dataGaName":1422,"dataGaLocation":1573},{"text":1424,"config":1775},{"href":1426,"dataGaName":1427,"dataGaLocation":1573},{"text":1429,"config":1777},{"href":1431,"dataGaName":1432,"dataGaLocation":1573},{"text":1779,"config":1780},"Sustainability",{"href":1781,"dataGaName":1779,"dataGaLocation":1573},"/sustainability/",{"text":1783,"config":1784},"Diversity, inclusion and belonging (DIB)",{"href":1785,"dataGaName":1786,"dataGaLocation":1573},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1434,"config":1788},{"href":1436,"dataGaName":1437,"dataGaLocation":1573},{"text":1444,"config":1790},{"href":1446,"dataGaName":1447,"dataGaLocation":1573},{"text":1449,"config":1792},{"href":1451,"dataGaName":1452,"dataGaLocation":1573},{"text":1794,"config":1795},"Modern Slavery Transparency Statement",{"href":1796,"dataGaName":1797,"dataGaLocation":1573},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1799},[1800,1803,1806],{"text":1801,"config":1802},"Terms",{"href":1625,"dataGaName":1626,"dataGaLocation":1573},{"text":1804,"config":1805},"Cookies",{"dataGaName":1635,"dataGaLocation":1573,"id":1636,"isOneTrustButton":952},{"text":1807,"config":1808},"Privacy",{"href":1630,"dataGaName":1631,"dataGaLocation":1573},[1810],{"id":1811,"title":1812,"body":1112,"config":1813,"content":1815,"description":1112,"extension":1818,"meta":1819,"navigation":952,"path":1820,"seo":1821,"stem":1822,"__hash__":1823},"blogAuthors/en-us/blog/authors/mario-de-la-ossa.yml","Mario De La Ossa",{"template":1814},"BlogAuthor",{"name":7,"config":1816},{"headshot":133,"ctfId":1817},"mdelaossa","yml",{},"/en-us/blog/authors/mario-de-la-ossa",{},"en-us/blog/authors/mario-de-la-ossa","2UY4H6UtHLbgIJgp3-Uf3gLcRl1G68QoLp5E8W57quM",[1825,1834,1842],{"title":1826,"description":1827,"heroImage":1828,"category":1108,"date":1829,"authors":1830,"slug":1833,"externalUrl":1112},"Confidential AI for GitLab Self-Hosted","Give developers AI coding agents in GitLab Duo without source code leaving a hardware-encrypted boundary — no GPUs needed.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1773866173/vte9qh8rriznvyclhkes.png","2026-08-06",[1831,1832],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1835,"description":1836,"heroImage":1837,"category":1108,"date":1838,"authors":1839,"slug":1841,"externalUrl":1112},"Green DevOps: Why carbon measurement belongs in your CI/CD pipeline","CI/CD pipelines have a hidden carbon cost. Here's why measuring it matters, and how you can get started with Eco CI and Carmen in GitLab.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1765809212/noh0mdfn9o94ry9ykura.png","2026-07-09",[1840],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1843,"description":1844,"heroImage":1845,"category":1108,"date":1846,"authors":1847,"slug":1849,"externalUrl":1112},"How to build CI/CD observability at scale","This practical guide to GitLab pipeline analytics helps self-managed users gain operational insights using Prometheus and Grafana.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1774465167/n5hlvrsrheadeccyr1oz.png","2026-04-28",[1848],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1851},[1852,1866,1878,1890],{"id":1853,"categories":1854,"header":1856,"text":1857,"button":1858,"image":1863},"ai-modernization",[1855],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1859,"config":1860},"Get your AI maturity score",{"href":1861,"dataGaName":1862,"dataGaLocation":1338},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1864},{"src":1865},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1867,"categories":1868,"header":1870,"text":1857,"button":1871,"image":1875},"devops-modernization",[1869,1674],"product","Are you just managing tools or shipping innovation?",{"text":1872,"config":1873},"Get your DevOps maturity score",{"href":1874,"dataGaName":1862,"dataGaLocation":1338},"/assessments/devops-modernization-assessment/",{"config":1876},{"src":1877},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1879,"categories":1880,"header":1882,"text":1857,"button":1883,"image":1887},"security-modernization",[1881],"security","Are you trading speed for security?",{"text":1884,"config":1885},"Get your security maturity score",{"href":1886,"dataGaName":1862,"dataGaLocation":1338},"/assessments/security-modernization-assessment/",{"config":1888},{"src":1889},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1891,"paths":1892,"header":1895,"text":1896,"button":1897,"image":1902},"github-azure-migration",[1893,1894],"migration-from-azure-devops-to-gitlab","integrating-azure-devops-scm-and-gitlab","Is your team ready for GitHub's Azure move?","GitHub is already rebuilding around Azure. Find out what it means for you.",{"text":1898,"config":1899},"See how GitLab compares to GitHub",{"href":1900,"dataGaName":1901,"dataGaLocation":1338},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1903},{"src":1877},{"header":1905,"blurb":1906,"button":1907,"secondaryButton":1912},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1908,"config":1909},"Get your free trial",{"href":1910,"dataGaName":1137,"dataGaLocation":1911},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1611,"config":1913},{"href":1463,"dataGaName":1142,"dataGaLocation":1911},1786803754148]