[{"data":1,"prerenderedAt":1689},["ShallowReactive",2],{"blog-en-gitlab-ci-container-deployment":3,"blog-en-gitlab-ci-container-deployment-alt":418},{"id":4,"title":5,"author":6,"body":7,"date":1673,"description":1674,"extension":1675,"image":75,"locale":1676,"meta":1677,"navigation":418,"path":1678,"seo":1679,"stem":1680,"tags":1681,"__hash__":1688},"blog\u002Fblog\u002Fen\u002Fgitlab-ci-container-deployment.md","How to Automate Container Deployment with GitLab ci-cd","Kubo Team",{"type":8,"value":9,"toc":1648},"minimark",[10,22,27,36,41,49,53,69,178,190,245,248,252,264,343,351,355,362,366,882,886,907,911,922,926,1030,1034,1041,1069,1077,1081,1084,1088,1151,1155,1327,1330,1334,1338,1416,1420,1591,1604,1608,1620,1624,1627,1644],[11,12,13,14,21],"p",{},"GitLab ci-cd processes over 400 million pipeline minutes monthly, serving more than 30 million developers on a unified DevOps platform. With source code management, ci-cd, container registry, and security scanning all integrated, GitLab enables efficient container deployment automation to Kubernetes. ",[15,16,20],"a",{"href":17,"rel":18},"https:\u002F\u002Fkubo.hexabase.io\u002F",[19],"nofollow","Kubo"," supports GitLab ci-cd integration, enabling enterprise-grade deployment pipelines. This article walks through the practical steps for container deployment automation with GitLab ci-cd.",[23,24,26],"h2",{"id":25},"secure-cluster-connection-with-the-gitlab-kubernetes-agent","Secure Cluster Connection with the GitLab Kubernetes Agent",[11,28,29,30,35],{},"Traditional Kubernetes integrations required exposing the cluster API server to the internet. The ",[15,31,34],{"href":32,"rel":33},"https:\u002F\u002Fdocs.gitlab.com\u002Fuser\u002Fclusters\u002Fagent\u002Fci_cd_workflow\u002F",[19],"GitLab Kubernetes Agent"," establishes a secure connection from inside the cluster, dramatically improving security.",[37,38,40],"h3",{"id":39},"agent-architecture","Agent Architecture",[11,42,43,44,48],{},"The GitLab Agent uses a ",[45,46,47],"strong",{},"pull-based architecture",". The agent runs inside the cluster and establishes an outbound connection to the GitLab server. This eliminates the need to expose the Kubernetes API server externally.",[37,50,52],{"id":51},"agent-setup-steps","Agent Setup Steps",[54,55,56,63],"ol",{},[57,58,59,62],"li",{},[45,60,61],{},"Register the agent",": Register an agent in your GitLab project and obtain a token",[57,64,65,68],{},[45,66,67],{},"Install with Helm",":",[70,71,76],"pre",{"className":72,"code":73,"language":74,"meta":75,"style":75},"language-bash shiki shiki-themes tokyo-night","helm repo add gitlab https:\u002F\u002Fcharts.gitlab.io\nhelm repo update\nhelm upgrade --install gitlab-agent gitlab\u002Fgitlab-agent \\\n  --namespace gitlab-agent --create-namespace \\\n  --set config.token=\u003Cagent-token> \\\n  --set config.kasAddress=wss:\u002F\u002Fkas.gitlab.com\n","bash","",[77,78,79,101,111,133,146,170],"code",{"__ignoreMap":75},[80,81,84,88,92,95,98],"span",{"class":82,"line":83},"line",1,[80,85,87],{"class":86},"sE3pS","helm",[80,89,91],{"class":90},"sPY7s"," repo",[80,93,94],{"class":90}," add",[80,96,97],{"class":90}," gitlab",[80,99,100],{"class":90}," https:\u002F\u002Fcharts.gitlab.io\n",[80,102,104,106,108],{"class":82,"line":103},2,[80,105,87],{"class":86},[80,107,91],{"class":90},[80,109,110],{"class":90}," update\n",[80,112,114,116,119,123,126,129],{"class":82,"line":113},3,[80,115,87],{"class":86},[80,117,118],{"class":90}," upgrade",[80,120,122],{"class":121},"sT800"," --install",[80,124,125],{"class":90}," gitlab-agent",[80,127,128],{"class":90}," gitlab\u002Fgitlab-agent",[80,130,132],{"class":131},"sAklC"," \\\n",[80,134,136,139,141,144],{"class":82,"line":135},4,[80,137,138],{"class":121},"  --namespace",[80,140,125],{"class":90},[80,142,143],{"class":121}," --create-namespace",[80,145,132],{"class":131},[80,147,149,152,155,158,161,165,168],{"class":82,"line":148},5,[80,150,151],{"class":121},"  --set",[80,153,154],{"class":90}," config.token=",[80,156,157],{"class":131},"\u003C",[80,159,160],{"class":90},"agent-toke",[80,162,164],{"class":163},"sGX4V","n",[80,166,167],{"class":131},">",[80,169,132],{"class":131},[80,171,173,175],{"class":82,"line":172},6,[80,174,151],{"class":121},[80,176,177],{"class":90}," config.kasAddress=wss:\u002F\u002Fkas.gitlab.com\n",[54,179,180],{"start":113},[57,181,182,185,186,189],{},[45,183,184],{},"Agent configuration file"," (",[77,187,188],{},".gitlab\u002Fagents\u002F\u003Cagent-name>\u002Fconfig.yaml","):",[70,191,195],{"className":192,"code":193,"language":194,"meta":75,"style":75},"language-yaml shiki shiki-themes tokyo-night","ci_access:\n  projects:\n    - id: your-group-your-project\n  groups:\n    - id: your-group\n","yaml",[77,196,197,206,213,227,234],{"__ignoreMap":75},[80,198,199,203],{"class":82,"line":83},[80,200,202],{"class":201},"s0U2E","ci_access",[80,204,205],{"class":131},":\n",[80,207,208,211],{"class":82,"line":103},[80,209,210],{"class":201},"  projects",[80,212,205],{"class":131},[80,214,215,219,222,224],{"class":82,"line":113},[80,216,218],{"class":217},"sgJMe","    -",[80,220,221],{"class":201}," id",[80,223,68],{"class":131},[80,225,226],{"class":90}," your-group-your-project\n",[80,228,229,232],{"class":82,"line":135},[80,230,231],{"class":201},"  groups",[80,233,205],{"class":131},[80,235,236,238,240,242],{"class":82,"line":148},[80,237,218],{"class":217},[80,239,221],{"class":201},[80,241,68],{"class":131},[80,243,244],{"class":90}," your-group\n",[11,246,247],{},"Up to 500 projects or groups can be linked to a single agent.",[37,249,251],{"id":250},"access-control-through-impersonation","Access Control Through Impersonation",[11,253,254,255,259,260,263],{},"According to the ",[15,256,258],{"href":32,"rel":257},[19],"GitLab official documentation",", ci-cd jobs inherit all permissions from the service account used to install the agent by default. In production environments, always configure ",[45,261,262],{},"Impersonation"," to apply least-privilege access per job.",[70,265,267],{"className":192,"code":266,"language":194,"meta":75,"style":75},"ci_access:\n  projects:\n    - id: your-group-your-project\n      default_namespace: production\n      access_as:\n        impersonate:\n          username: gitlab-ci-deploy\n          groups:\n            - deployers\n",[77,268,269,275,281,291,301,308,315,326,334],{"__ignoreMap":75},[80,270,271,273],{"class":82,"line":83},[80,272,202],{"class":201},[80,274,205],{"class":131},[80,276,277,279],{"class":82,"line":103},[80,278,210],{"class":201},[80,280,205],{"class":131},[80,282,283,285,287,289],{"class":82,"line":113},[80,284,218],{"class":217},[80,286,221],{"class":201},[80,288,68],{"class":131},[80,290,226],{"class":90},[80,292,293,296,298],{"class":82,"line":135},[80,294,295],{"class":201},"      default_namespace",[80,297,68],{"class":131},[80,299,300],{"class":90}," production\n",[80,302,303,306],{"class":82,"line":148},[80,304,305],{"class":201},"      access_as",[80,307,205],{"class":131},[80,309,310,313],{"class":82,"line":172},[80,311,312],{"class":201},"        impersonate",[80,314,205],{"class":131},[80,316,318,321,323],{"class":82,"line":317},7,[80,319,320],{"class":201},"          username",[80,322,68],{"class":131},[80,324,325],{"class":90}," gitlab-ci-deploy\n",[80,327,329,332],{"class":82,"line":328},8,[80,330,331],{"class":201},"          groups",[80,333,205],{"class":131},[80,335,337,340],{"class":82,"line":336},9,[80,338,339],{"class":217},"            -",[80,341,342],{"class":90}," deployers\n",[11,344,345,350],{},[15,346,349],{"href":347,"rel":348},"https:\u002F\u002Fwww.hexabase.com\u002Fproduct\u002Fcaptain-ai\u002F",[19],"Captain.AI"," helps teams navigate complex access control configurations with AI-guided setup assistance.",[23,352,354],{"id":353},"building-pipelines-with-gitlab-ciyml","Building Pipelines with .gitlab-ci.yml",[11,356,357,358,361],{},"GitLab ci-cd pipelines are defined in ",[77,359,360],{},".gitlab-ci.yml",". Let's examine a typical pipeline configuration for container deployment.",[37,363,365],{"id":364},"basic-pipeline-configuration","Basic Pipeline Configuration",[70,367,369],{"className":192,"code":368,"language":194,"meta":75,"style":75},"stages:\n  - build\n  - test\n  - scan\n  - deploy-staging\n  - deploy-production\n\nvariables:\n  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n  KUBE_CONTEXT: your-group\u002Fyour-project:production-agent\n\nbuild:\n  stage: build\n  image: docker:27\n  services:\n    - docker:27-dind\n  script:\n    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY\n    - docker build -t $IMAGE_TAG .\n    - docker push $IMAGE_TAG\n\ntest:\n  stage: test\n  image: $IMAGE_TAG\n  script:\n    - npm ci\n    - npm test\n\ncontainer-scan:\n  stage: scan\n  image:\n    name: aquasec\u002Ftrivy:latest\n    entrypoint: [\"\"]\n  script:\n    - trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG\n\ndeploy-staging:\n  stage: deploy-staging\n  image:\n    name: bitnami\u002Fkubectl:latest\n    entrypoint: [\"\"]\n  script:\n    - kubectl config use-context $KUBE_CONTEXT\n    - kubectl set image deployment-my-app app=$image_tag -n staging\n    - kubectl rollout status deployment-my-app -n staging\n  environment:\n    name: staging\n\ndeploy-production:\n  stage: deploy-production\n  image:\n    name: bitnami\u002Fkubectl:latest\n    entrypoint: [\"\"]\n  script:\n    - kubectl config use-context $KUBE_CONTEXT\n    - kubectl set image deployment-my-app app=$image_tag -n production\n    - kubectl rollout status deployment-my-app -n production\n  environment:\n    name: production\n  when: manual\n  only:\n    - main\n",[77,370,371,378,386,393,400,407,414,420,427,437,448,453,461,471,482,490,498,506,514,522,530,535,543,552,562,569,577,585,590,598,607,614,625,642,649,657,662,670,679,686,696,709,716,724,732,740,748,758,763,771,780,787,796,809,816,823,831,839,846,855,866,874],{"__ignoreMap":75},[80,372,373,376],{"class":82,"line":83},[80,374,375],{"class":201},"stages",[80,377,205],{"class":131},[80,379,380,383],{"class":82,"line":103},[80,381,382],{"class":217},"  -",[80,384,385],{"class":90}," build\n",[80,387,388,390],{"class":82,"line":113},[80,389,382],{"class":217},[80,391,392],{"class":90}," test\n",[80,394,395,397],{"class":82,"line":135},[80,396,382],{"class":217},[80,398,399],{"class":90}," scan\n",[80,401,402,404],{"class":82,"line":148},[80,403,382],{"class":217},[80,405,406],{"class":90}," deploy-staging\n",[80,408,409,411],{"class":82,"line":172},[80,410,382],{"class":217},[80,412,413],{"class":90}," deploy-production\n",[80,415,416],{"class":82,"line":317},[80,417,419],{"emptyLinePlaceholder":418},true,"\n",[80,421,422,425],{"class":82,"line":328},[80,423,424],{"class":201},"variables",[80,426,205],{"class":131},[80,428,429,432,434],{"class":82,"line":336},[80,430,431],{"class":201},"  IMAGE_TAG",[80,433,68],{"class":131},[80,435,436],{"class":90}," $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA\n",[80,438,440,443,445],{"class":82,"line":439},10,[80,441,442],{"class":201},"  KUBE_CONTEXT",[80,444,68],{"class":131},[80,446,447],{"class":90}," your-group\u002Fyour-project:production-agent\n",[80,449,451],{"class":82,"line":450},11,[80,452,419],{"emptyLinePlaceholder":418},[80,454,456,459],{"class":82,"line":455},12,[80,457,458],{"class":201},"build",[80,460,205],{"class":131},[80,462,464,467,469],{"class":82,"line":463},13,[80,465,466],{"class":201},"  stage",[80,468,68],{"class":131},[80,470,385],{"class":90},[80,472,474,477,479],{"class":82,"line":473},14,[80,475,476],{"class":201},"  image",[80,478,68],{"class":131},[80,480,481],{"class":90}," docker:27\n",[80,483,485,488],{"class":82,"line":484},15,[80,486,487],{"class":201},"  services",[80,489,205],{"class":131},[80,491,493,495],{"class":82,"line":492},16,[80,494,218],{"class":217},[80,496,497],{"class":90}," docker:27-dind\n",[80,499,501,504],{"class":82,"line":500},17,[80,502,503],{"class":201},"  script",[80,505,205],{"class":131},[80,507,509,511],{"class":82,"line":508},18,[80,510,218],{"class":217},[80,512,513],{"class":90}," docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY\n",[80,515,517,519],{"class":82,"line":516},19,[80,518,218],{"class":217},[80,520,521],{"class":90}," docker build -t $IMAGE_TAG .\n",[80,523,525,527],{"class":82,"line":524},20,[80,526,218],{"class":217},[80,528,529],{"class":90}," docker push $IMAGE_TAG\n",[80,531,533],{"class":82,"line":532},21,[80,534,419],{"emptyLinePlaceholder":418},[80,536,538,541],{"class":82,"line":537},22,[80,539,540],{"class":201},"test",[80,542,205],{"class":131},[80,544,546,548,550],{"class":82,"line":545},23,[80,547,466],{"class":201},[80,549,68],{"class":131},[80,551,392],{"class":90},[80,553,555,557,559],{"class":82,"line":554},24,[80,556,476],{"class":201},[80,558,68],{"class":131},[80,560,561],{"class":90}," $IMAGE_TAG\n",[80,563,565,567],{"class":82,"line":564},25,[80,566,503],{"class":201},[80,568,205],{"class":131},[80,570,572,574],{"class":82,"line":571},26,[80,573,218],{"class":217},[80,575,576],{"class":90}," npm ci\n",[80,578,580,582],{"class":82,"line":579},27,[80,581,218],{"class":217},[80,583,584],{"class":90}," npm test\n",[80,586,588],{"class":82,"line":587},28,[80,589,419],{"emptyLinePlaceholder":418},[80,591,593,596],{"class":82,"line":592},29,[80,594,595],{"class":201},"container-scan",[80,597,205],{"class":131},[80,599,601,603,605],{"class":82,"line":600},30,[80,602,466],{"class":201},[80,604,68],{"class":131},[80,606,399],{"class":90},[80,608,610,612],{"class":82,"line":609},31,[80,611,476],{"class":201},[80,613,205],{"class":131},[80,615,617,620,622],{"class":82,"line":616},32,[80,618,619],{"class":201},"    name",[80,621,68],{"class":131},[80,623,624],{"class":90}," aquasec\u002Ftrivy:latest\n",[80,626,628,631,633,636,639],{"class":82,"line":627},33,[80,629,630],{"class":201},"    entrypoint",[80,632,68],{"class":131},[80,634,635],{"class":131}," [",[80,637,638],{"class":131},"\"\"",[80,640,641],{"class":131},"]\n",[80,643,645,647],{"class":82,"line":644},34,[80,646,503],{"class":201},[80,648,205],{"class":131},[80,650,652,654],{"class":82,"line":651},35,[80,653,218],{"class":217},[80,655,656],{"class":90}," trivy image --exit-code 1 --severity HIGH,CRITICAL $IMAGE_TAG\n",[80,658,660],{"class":82,"line":659},36,[80,661,419],{"emptyLinePlaceholder":418},[80,663,665,668],{"class":82,"line":664},37,[80,666,667],{"class":201},"deploy-staging",[80,669,205],{"class":131},[80,671,673,675,677],{"class":82,"line":672},38,[80,674,466],{"class":201},[80,676,68],{"class":131},[80,678,406],{"class":90},[80,680,682,684],{"class":82,"line":681},39,[80,683,476],{"class":201},[80,685,205],{"class":131},[80,687,689,691,693],{"class":82,"line":688},40,[80,690,619],{"class":201},[80,692,68],{"class":131},[80,694,695],{"class":90}," bitnami\u002Fkubectl:latest\n",[80,697,699,701,703,705,707],{"class":82,"line":698},41,[80,700,630],{"class":201},[80,702,68],{"class":131},[80,704,635],{"class":131},[80,706,638],{"class":131},[80,708,641],{"class":131},[80,710,712,714],{"class":82,"line":711},42,[80,713,503],{"class":201},[80,715,205],{"class":131},[80,717,719,721],{"class":82,"line":718},43,[80,720,218],{"class":217},[80,722,723],{"class":90}," kubectl config use-context $KUBE_CONTEXT\n",[80,725,727,729],{"class":82,"line":726},44,[80,728,218],{"class":217},[80,730,731],{"class":90}," kubectl set image deployment-my-app app=$image_tag -n staging\n",[80,733,735,737],{"class":82,"line":734},45,[80,736,218],{"class":217},[80,738,739],{"class":90}," kubectl rollout status deployment-my-app -n staging\n",[80,741,743,746],{"class":82,"line":742},46,[80,744,745],{"class":201},"  environment",[80,747,205],{"class":131},[80,749,751,753,755],{"class":82,"line":750},47,[80,752,619],{"class":201},[80,754,68],{"class":131},[80,756,757],{"class":90}," staging\n",[80,759,761],{"class":82,"line":760},48,[80,762,419],{"emptyLinePlaceholder":418},[80,764,766,769],{"class":82,"line":765},49,[80,767,768],{"class":201},"deploy-production",[80,770,205],{"class":131},[80,772,774,776,778],{"class":82,"line":773},50,[80,775,466],{"class":201},[80,777,68],{"class":131},[80,779,413],{"class":90},[80,781,783,785],{"class":82,"line":782},51,[80,784,476],{"class":201},[80,786,205],{"class":131},[80,788,790,792,794],{"class":82,"line":789},52,[80,791,619],{"class":201},[80,793,68],{"class":131},[80,795,695],{"class":90},[80,797,799,801,803,805,807],{"class":82,"line":798},53,[80,800,630],{"class":201},[80,802,68],{"class":131},[80,804,635],{"class":131},[80,806,638],{"class":131},[80,808,641],{"class":131},[80,810,812,814],{"class":82,"line":811},54,[80,813,503],{"class":201},[80,815,205],{"class":131},[80,817,819,821],{"class":82,"line":818},55,[80,820,218],{"class":217},[80,822,723],{"class":90},[80,824,826,828],{"class":82,"line":825},56,[80,827,218],{"class":217},[80,829,830],{"class":90}," kubectl set image deployment-my-app app=$image_tag -n production\n",[80,832,834,836],{"class":82,"line":833},57,[80,835,218],{"class":217},[80,837,838],{"class":90}," kubectl rollout status deployment-my-app -n production\n",[80,840,842,844],{"class":82,"line":841},58,[80,843,745],{"class":201},[80,845,205],{"class":131},[80,847,849,851,853],{"class":82,"line":848},59,[80,850,619],{"class":201},[80,852,68],{"class":131},[80,854,300],{"class":90},[80,856,858,861,863],{"class":82,"line":857},60,[80,859,860],{"class":201},"  when",[80,862,68],{"class":131},[80,864,865],{"class":90}," manual\n",[80,867,869,872],{"class":82,"line":868},61,[80,870,871],{"class":201},"  only",[80,873,205],{"class":131},[80,875,877,879],{"class":82,"line":876},62,[80,878,218],{"class":217},[80,880,881],{"class":90}," main\n",[37,883,885],{"id":884},"leveraging-the-gitlab-container-registry","Leveraging the GitLab Container Registry",[11,887,888,889,894,895,898,899,902,903,906],{},"GitLab includes a ",[15,890,893],{"href":891,"rel":892},"https:\u002F\u002Fdocs.gitlab.com\u002Fuser\u002Fpackages\u002Fcontainer_registry\u002F",[19],"built-in container registry",", eliminating the need for additional registry services. Predefined variables like ",[77,896,897],{},"$CI_REGISTRY",", ",[77,900,901],{},"$CI_REGISTRY_USER",", and ",[77,904,905],{},"$CI_REGISTRY_PASSWORD"," enable seamless authentication.",[23,908,910],{"id":909},"automatic-pipelines-with-auto-devops","Automatic Pipelines with Auto DevOps",[11,912,913,918,919,921],{},[15,914,917],{"href":915,"rel":916},"https:\u002F\u002Fdocs.gitlab.com\u002Ftopics\u002Fautodevops\u002F",[19],"GitLab Auto DevOps"," builds pipelines automatically by detecting your application's language and framework---without writing a single line in ",[77,920,360],{},".",[37,923,925],{"id":924},"auto-devops-stages","Auto DevOps Stages",[927,928,929,945],"table",{},[930,931,932],"thead",{},[933,934,935,939,942],"tr",{},[936,937,938],"th",{},"Stage",[936,940,941],{},"Function",[936,943,944],{},"Tool",[946,947,948,960,971,982,993,1008,1019],"tbody",{},[933,949,950,954,957],{},[951,952,953],"td",{},"Auto Build",[951,955,956],{},"Dockerfile or buildpack detection",[951,958,959],{},"Cloud Native Buildpacks",[933,961,962,965,968],{},[951,963,964],{},"Auto Test",[951,966,967],{},"Language-specific test execution",[951,969,970],{},"Language-specific tools",[933,972,973,976,979],{},[951,974,975],{},"Auto Code Quality",[951,977,978],{},"Code quality analysis",[951,980,981],{},"Code Climate",[933,983,984,987,990],{},[951,985,986],{},"Auto SAST",[951,988,989],{},"Static security testing",[951,991,992],{},"GitLab SAST",[933,994,995,998,1001],{},[951,996,997],{},"Auto Container Scanning",[951,999,1000],{},"Container vulnerability scanning",[951,1002,1003],{},[15,1004,1007],{"href":1005,"rel":1006},"https:\u002F\u002Ftrivy.dev\u002F",[19],"Trivy",[933,1009,1010,1013,1016],{},[951,1011,1012],{},"Auto Deploy",[951,1014,1015],{},"Kubernetes deployment",[951,1017,1018],{},"Helm",[933,1020,1021,1024,1027],{},[951,1022,1023],{},"Auto Monitoring",[951,1025,1026],{},"Runtime monitoring",[951,1028,1029],{},"Prometheus",[37,1031,1033],{"id":1032},"integration-with-the-kubernetes-agent","Integration with the Kubernetes Agent",[11,1035,1036,1037,1040],{},"When using the Kubernetes Agent with Auto DevOps, specify the agent context via the ",[77,1038,1039],{},"KUBE_CONTEXT"," variable.",[70,1042,1044],{"className":192,"code":1043,"language":194,"meta":75,"style":75},"# Set in ci-cd Variables\nKUBE_CONTEXT: your-group\u002Fyour-project:staging-agent  # For staging\n# A different agent can be specified for the production environment\n",[77,1045,1046,1052,1064],{"__ignoreMap":75},[80,1047,1048],{"class":82,"line":83},[80,1049,1051],{"class":1050},"sbD-w","# Set in ci-cd Variables\n",[80,1053,1054,1056,1058,1061],{"class":82,"line":103},[80,1055,1039],{"class":201},[80,1057,68],{"class":131},[80,1059,1060],{"class":90}," your-group\u002Fyour-project:staging-agent",[80,1062,1063],{"class":1050},"  # For staging\n",[80,1065,1066],{"class":82,"line":113},[80,1067,1068],{"class":1050},"# A different agent can be specified for the production environment\n",[11,1070,1071,1072,1076],{},"In ",[15,1073,1075],{"href":17,"rel":1074},[19],"Kubo's"," managed environment, Auto DevOps settings come pre-optimized, letting you start automatic pipelines right after project creation.",[23,1078,1080],{"id":1079},"multi-environment-deployment-strategies","Multi-Environment Deployment Strategies",[11,1082,1083],{},"Production-grade deployments require different configurations and approval flows per environment.",[37,1085,1087],{"id":1086},"environment-scoped-variables","Environment-Scoped Variables",[70,1089,1091],{"className":192,"code":1090,"language":194,"meta":75,"style":75},"# GitLab ci-cd Variables (configured in UI)\n# Environment: staging\nREPLICAS: 1\nDB_HOST: staging-db.internal\n\n# Environment: production\nREPLICAS: 3\nDB_HOST: production-db.internal\n",[77,1092,1093,1098,1103,1114,1124,1128,1133,1142],{"__ignoreMap":75},[80,1094,1095],{"class":82,"line":83},[80,1096,1097],{"class":1050},"# GitLab ci-cd Variables (configured in UI)\n",[80,1099,1100],{"class":82,"line":103},[80,1101,1102],{"class":1050},"# Environment: staging\n",[80,1104,1105,1108,1110],{"class":82,"line":113},[80,1106,1107],{"class":201},"REPLICAS",[80,1109,68],{"class":131},[80,1111,1113],{"class":1112},"sOJ5S"," 1\n",[80,1115,1116,1119,1121],{"class":82,"line":135},[80,1117,1118],{"class":201},"DB_HOST",[80,1120,68],{"class":131},[80,1122,1123],{"class":90}," staging-db.internal\n",[80,1125,1126],{"class":82,"line":148},[80,1127,419],{"emptyLinePlaceholder":418},[80,1129,1130],{"class":82,"line":172},[80,1131,1132],{"class":1050},"# Environment: production\n",[80,1134,1135,1137,1139],{"class":82,"line":317},[80,1136,1107],{"class":201},[80,1138,68],{"class":131},[80,1140,1141],{"class":1112}," 3\n",[80,1143,1144,1146,1148],{"class":82,"line":328},[80,1145,1118],{"class":201},[80,1147,68],{"class":131},[80,1149,1150],{"class":90}," production-db.internal\n",[37,1152,1154],{"id":1153},"dynamic-environments-with-review-apps","Dynamic Environments with Review Apps",[70,1156,1158],{"className":192,"code":1157,"language":194,"meta":75,"style":75},"review:\n  stage: deploy-staging\n  script:\n    - kubectl config use-context $KUBE_CONTEXT\n    - helm upgrade --install review-$ci_commit_ref_slug .-chart \\\n        --namespace review \\\n        --set image.tag=$CI_COMMIT_SHA \\\n        --set ingress.host=$CI_COMMIT_REF_SLUG.review.example.com\n  environment:\n    name: review\u002F$CI_COMMIT_REF_SLUG\n    url: https:\u002F\u002F$CI_COMMIT_REF_SLUG.review.example.com\n    on_stop: stop-review\n  only:\n    - merge_requests\n\nstop-review:\n  stage: deploy-staging\n  script:\n    - kubectl config use-context $KUBE_CONTEXT\n    - helm uninstall review-$CI_COMMIT_REF_SLUG --namespace review\n  environment:\n    name: review\u002F$CI_COMMIT_REF_SLUG\n    action: stop\n  when: manual\n",[77,1159,1160,1167,1175,1181,1187,1194,1199,1204,1209,1215,1224,1234,1244,1250,1257,1261,1268,1276,1282,1288,1295,1301,1309,1319],{"__ignoreMap":75},[80,1161,1162,1165],{"class":82,"line":83},[80,1163,1164],{"class":201},"review",[80,1166,205],{"class":131},[80,1168,1169,1171,1173],{"class":82,"line":103},[80,1170,466],{"class":201},[80,1172,68],{"class":131},[80,1174,406],{"class":90},[80,1176,1177,1179],{"class":82,"line":113},[80,1178,503],{"class":201},[80,1180,205],{"class":131},[80,1182,1183,1185],{"class":82,"line":135},[80,1184,218],{"class":217},[80,1186,723],{"class":90},[80,1188,1189,1191],{"class":82,"line":148},[80,1190,218],{"class":217},[80,1192,1193],{"class":90}," helm upgrade --install review-$ci_commit_ref_slug .-chart \\\n",[80,1195,1196],{"class":82,"line":172},[80,1197,1198],{"class":90},"        --namespace review \\\n",[80,1200,1201],{"class":82,"line":317},[80,1202,1203],{"class":90},"        --set image.tag=$CI_COMMIT_SHA \\\n",[80,1205,1206],{"class":82,"line":328},[80,1207,1208],{"class":90},"        --set ingress.host=$CI_COMMIT_REF_SLUG.review.example.com\n",[80,1210,1211,1213],{"class":82,"line":336},[80,1212,745],{"class":201},[80,1214,205],{"class":131},[80,1216,1217,1219,1221],{"class":82,"line":439},[80,1218,619],{"class":201},[80,1220,68],{"class":131},[80,1222,1223],{"class":90}," review\u002F$CI_COMMIT_REF_SLUG\n",[80,1225,1226,1229,1231],{"class":82,"line":450},[80,1227,1228],{"class":201},"    url",[80,1230,68],{"class":131},[80,1232,1233],{"class":90}," https:\u002F\u002F$CI_COMMIT_REF_SLUG.review.example.com\n",[80,1235,1236,1239,1241],{"class":82,"line":455},[80,1237,1238],{"class":201},"    on_stop",[80,1240,68],{"class":131},[80,1242,1243],{"class":90}," stop-review\n",[80,1245,1246,1248],{"class":82,"line":463},[80,1247,871],{"class":201},[80,1249,205],{"class":131},[80,1251,1252,1254],{"class":82,"line":473},[80,1253,218],{"class":217},[80,1255,1256],{"class":90}," merge_requests\n",[80,1258,1259],{"class":82,"line":484},[80,1260,419],{"emptyLinePlaceholder":418},[80,1262,1263,1266],{"class":82,"line":492},[80,1264,1265],{"class":201},"stop-review",[80,1267,205],{"class":131},[80,1269,1270,1272,1274],{"class":82,"line":500},[80,1271,466],{"class":201},[80,1273,68],{"class":131},[80,1275,406],{"class":90},[80,1277,1278,1280],{"class":82,"line":508},[80,1279,503],{"class":201},[80,1281,205],{"class":131},[80,1283,1284,1286],{"class":82,"line":516},[80,1285,218],{"class":217},[80,1287,723],{"class":90},[80,1289,1290,1292],{"class":82,"line":524},[80,1291,218],{"class":217},[80,1293,1294],{"class":90}," helm uninstall review-$CI_COMMIT_REF_SLUG --namespace review\n",[80,1296,1297,1299],{"class":82,"line":532},[80,1298,745],{"class":201},[80,1300,205],{"class":131},[80,1302,1303,1305,1307],{"class":82,"line":537},[80,1304,619],{"class":201},[80,1306,68],{"class":131},[80,1308,1223],{"class":90},[80,1310,1311,1314,1316],{"class":82,"line":545},[80,1312,1313],{"class":201},"    action",[80,1315,68],{"class":131},[80,1317,1318],{"class":90}," stop\n",[80,1320,1321,1323,1325],{"class":82,"line":554},[80,1322,860],{"class":201},[80,1324,68],{"class":131},[80,1326,865],{"class":90},[11,1328,1329],{},"Each merge request gets its own dynamic review environment where reviewers can test the actual application. Cleanup happens automatically or manually after merging.",[23,1331,1333],{"id":1332},"pipeline-optimization-and-cost-efficiency","Pipeline Optimization and Cost Efficiency",[37,1335,1337],{"id":1336},"caching-strategy","Caching Strategy",[70,1339,1341],{"className":192,"code":1340,"language":194,"meta":75,"style":75},"build:\n  stage: build\n  cache:\n    key: $CI_COMMIT_REF_SLUG\n    paths:\n      - node_modules\u002F\n      - .npm\u002F\n  script:\n    - npm ci --cache .npm --prefer-offline\n    - npm run build\n",[77,1342,1343,1349,1357,1364,1374,1381,1389,1396,1402,1409],{"__ignoreMap":75},[80,1344,1345,1347],{"class":82,"line":83},[80,1346,458],{"class":201},[80,1348,205],{"class":131},[80,1350,1351,1353,1355],{"class":82,"line":103},[80,1352,466],{"class":201},[80,1354,68],{"class":131},[80,1356,385],{"class":90},[80,1358,1359,1362],{"class":82,"line":113},[80,1360,1361],{"class":201},"  cache",[80,1363,205],{"class":131},[80,1365,1366,1369,1371],{"class":82,"line":135},[80,1367,1368],{"class":201},"    key",[80,1370,68],{"class":131},[80,1372,1373],{"class":90}," $CI_COMMIT_REF_SLUG\n",[80,1375,1376,1379],{"class":82,"line":148},[80,1377,1378],{"class":201},"    paths",[80,1380,205],{"class":131},[80,1382,1383,1386],{"class":82,"line":172},[80,1384,1385],{"class":217},"      -",[80,1387,1388],{"class":90}," node_modules\u002F\n",[80,1390,1391,1393],{"class":82,"line":317},[80,1392,1385],{"class":217},[80,1394,1395],{"class":90}," .npm\u002F\n",[80,1397,1398,1400],{"class":82,"line":328},[80,1399,503],{"class":201},[80,1401,205],{"class":131},[80,1403,1404,1406],{"class":82,"line":336},[80,1405,218],{"class":217},[80,1407,1408],{"class":90}," npm ci --cache .npm --prefer-offline\n",[80,1410,1411,1413],{"class":82,"line":439},[80,1412,218],{"class":217},[80,1414,1415],{"class":90}," npm run build\n",[37,1417,1419],{"id":1418},"parallelization-with-dag","Parallelization with DAG",[70,1421,1423],{"className":192,"code":1422,"language":194,"meta":75,"style":75},"stages:\n  - build\n  - test\n  - deploy\n\nlint:\n  stage: test\n  needs: []  # Runs without waiting for build\n  script:\n    - npm run lint\n\nunit-test:\n  stage: test\n  needs: [\"build\"]\n  script:\n    - npm test\n\ne2e-test:\n  stage: test\n  needs: [\"build\"]\n  script:\n    - npm run e2e\n",[77,1424,1425,1431,1437,1443,1450,1454,1461,1469,1482,1488,1495,1499,1506,1514,1531,1537,1543,1547,1554,1562,1578,1584],{"__ignoreMap":75},[80,1426,1427,1429],{"class":82,"line":83},[80,1428,375],{"class":201},[80,1430,205],{"class":131},[80,1432,1433,1435],{"class":82,"line":103},[80,1434,382],{"class":217},[80,1436,385],{"class":90},[80,1438,1439,1441],{"class":82,"line":113},[80,1440,382],{"class":217},[80,1442,392],{"class":90},[80,1444,1445,1447],{"class":82,"line":135},[80,1446,382],{"class":217},[80,1448,1449],{"class":90}," deploy\n",[80,1451,1452],{"class":82,"line":148},[80,1453,419],{"emptyLinePlaceholder":418},[80,1455,1456,1459],{"class":82,"line":172},[80,1457,1458],{"class":201},"lint",[80,1460,205],{"class":131},[80,1462,1463,1465,1467],{"class":82,"line":317},[80,1464,466],{"class":201},[80,1466,68],{"class":131},[80,1468,392],{"class":90},[80,1470,1471,1474,1476,1479],{"class":82,"line":328},[80,1472,1473],{"class":201},"  needs",[80,1475,68],{"class":131},[80,1477,1478],{"class":131}," []",[80,1480,1481],{"class":1050},"  # Runs without waiting for build\n",[80,1483,1484,1486],{"class":82,"line":336},[80,1485,503],{"class":201},[80,1487,205],{"class":131},[80,1489,1490,1492],{"class":82,"line":439},[80,1491,218],{"class":217},[80,1493,1494],{"class":90}," npm run lint\n",[80,1496,1497],{"class":82,"line":450},[80,1498,419],{"emptyLinePlaceholder":418},[80,1500,1501,1504],{"class":82,"line":455},[80,1502,1503],{"class":201},"unit-test",[80,1505,205],{"class":131},[80,1507,1508,1510,1512],{"class":82,"line":463},[80,1509,466],{"class":201},[80,1511,68],{"class":131},[80,1513,392],{"class":90},[80,1515,1516,1518,1520,1522,1525,1527,1529],{"class":82,"line":473},[80,1517,1473],{"class":201},[80,1519,68],{"class":131},[80,1521,635],{"class":131},[80,1523,1524],{"class":131},"\"",[80,1526,458],{"class":90},[80,1528,1524],{"class":131},[80,1530,641],{"class":131},[80,1532,1533,1535],{"class":82,"line":484},[80,1534,503],{"class":201},[80,1536,205],{"class":131},[80,1538,1539,1541],{"class":82,"line":492},[80,1540,218],{"class":217},[80,1542,584],{"class":90},[80,1544,1545],{"class":82,"line":500},[80,1546,419],{"emptyLinePlaceholder":418},[80,1548,1549,1552],{"class":82,"line":508},[80,1550,1551],{"class":201},"e2e-test",[80,1553,205],{"class":131},[80,1555,1556,1558,1560],{"class":82,"line":516},[80,1557,466],{"class":201},[80,1559,68],{"class":131},[80,1561,392],{"class":90},[80,1563,1564,1566,1568,1570,1572,1574,1576],{"class":82,"line":524},[80,1565,1473],{"class":201},[80,1567,68],{"class":131},[80,1569,635],{"class":131},[80,1571,1524],{"class":131},[80,1573,458],{"class":90},[80,1575,1524],{"class":131},[80,1577,641],{"class":131},[80,1579,1580,1582],{"class":82,"line":532},[80,1581,503],{"class":201},[80,1583,205],{"class":131},[80,1585,1586,1588],{"class":82,"line":537},[80,1587,218],{"class":217},[80,1589,1590],{"class":90}," npm run e2e\n",[11,1592,1593,1594,1599,1600,1603],{},"Build ",[15,1595,1598],{"href":1596,"rel":1597},"https:\u002F\u002Fdocs.gitlab.com\u002Fci\u002Fdirected_acyclic_graph\u002F",[19],"DAG pipelines"," with the ",[77,1601,1602],{},"needs"," keyword to eliminate unnecessary wait times.",[37,1605,1607],{"id":1606},"gitlab-runner-scaling","GitLab Runner Scaling",[11,1609,1610,1615,1616,1619],{},[15,1611,1614],{"href":1612,"rel":1613},"https:\u002F\u002Faws.amazon.com\u002Fblogs\u002Fcontainers\u002Fstreamline-your-containerized-ci-cd-with-gitlab-runners-and-amazon-eks-auto-mode\u002F",[19],"Integration with Amazon EKS Auto Mode"," enables up to 90% cost reduction through optimized Spot Instance usage. ",[15,1617,20],{"href":17,"rel":1618},[19]," includes similar runner optimization capabilities built in.",[23,1621,1623],{"id":1622},"conclusion-get-started-with-gitlab-ci-cd-on-kubo","Conclusion: Get Started with GitLab ci-cd on Kubo",[11,1625,1626],{},"GitLab ci-cd provides powerful container deployment automation as a unified DevOps platform. With secure Kubernetes Agent connections, automatic Auto DevOps pipelines, dynamic Review App environments, and DAG-based parallelization, you can build enterprise-grade deployment flows.",[11,1628,1629,1632,1633,1637,1638,1643],{},[15,1630,20],{"href":17,"rel":1631},[19]," natively supports GitLab ci-cd integration, from Agent configuration to multi-environment deployment. ",[15,1634,1636],{"href":347,"rel":1635},[19],"Captain.AI's"," AI assistant helps optimize your pipelines, boosting GitLab ci-cd productivity even further. Ready to automate your container deployments? ",[15,1639,1642],{"href":1640,"rel":1641},"https:\u002F\u002Fwww.hexabase.com\u002Fcontact-us\u002F",[19],"Contact us"," today.",[1645,1646,1647],"style",{},"html pre.shiki code .sE3pS, html code.shiki .sE3pS{--shiki-default:#C0CAF5}html pre.shiki code .sPY7s, html code.shiki .sPY7s{--shiki-default:#9ECE6A}html pre.shiki code .sT800, html code.shiki .sT800{--shiki-default:#E0AF68}html pre.shiki code .sAklC, html code.shiki .sAklC{--shiki-default:#89DDFF}html pre.shiki code .sGX4V, html code.shiki .sGX4V{--shiki-default:#A9B1D6}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 .s0U2E, html code.shiki .s0U2E{--shiki-default:#F7768E}html pre.shiki code .sgJMe, html code.shiki .sgJMe{--shiki-default:#9ABDF5}html pre.shiki code .sbD-w, html code.shiki .sbD-w{--shiki-default:#51597D;--shiki-default-font-style:italic}html pre.shiki code .sOJ5S, html code.shiki .sOJ5S{--shiki-default:#FF9E64}",{"title":75,"searchDepth":103,"depth":103,"links":1649},[1650,1655,1659,1663,1667,1672],{"id":25,"depth":103,"text":26,"children":1651},[1652,1653,1654],{"id":39,"depth":113,"text":40},{"id":51,"depth":113,"text":52},{"id":250,"depth":113,"text":251},{"id":353,"depth":103,"text":354,"children":1656},[1657,1658],{"id":364,"depth":113,"text":365},{"id":884,"depth":113,"text":885},{"id":909,"depth":103,"text":910,"children":1660},[1661,1662],{"id":924,"depth":113,"text":925},{"id":1032,"depth":113,"text":1033},{"id":1079,"depth":103,"text":1080,"children":1664},[1665,1666],{"id":1086,"depth":113,"text":1087},{"id":1153,"depth":113,"text":1154},{"id":1332,"depth":103,"text":1333,"children":1668},[1669,1670,1671],{"id":1336,"depth":113,"text":1337},{"id":1418,"depth":113,"text":1419},{"id":1606,"depth":113,"text":1607},{"id":1622,"depth":103,"text":1623},"2026-05-27","A practical guide to automating container deployment with GitLab ci-cd and the Kubernetes Agent. Covers pipeline setup, Auto DevOps, security, and multi-environment strategies.","md","en",{},"\u002Fblog\u002Fen\u002Fgitlab-ci-container-deployment",{"title":5,"description":1674},"blog\u002Fen\u002Fgitlab-ci-container-deployment",[1682,1683,1684,1685,1686,1687],"GitLab","ci-cd","Kubernetes","Containers","Auto DevOps","Deployment Automation","v5Q0rJKzbFkGomJdusxAE1TSHfxXGWMrCu9igHbXMcg",1779964618862]