[{"data":1,"prerenderedAt":1736},["ShallowReactive",2],{"blog-en-gitlab-ci-container-deployment":3,"blog-related-en-gitlab-ci-container-deployment":1689,"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","UKXgp9BPnSPqlMAA10L9WhGEc2xTHIAxHQLWfWHJfU0",[1690,1699,1707,1714,1721,1728],{"path":1691,"title":1692,"description":1693,"date":1694,"tags":1695},"\u002Fblog\u002Fen\u002Fgitlab-auto-devops-k3s-admission-controller-gate","Why GitLab Auto DevOps' 'It Just Works' CI Is the Closest Threat to Your Production K3s Cluster","GitLab Auto DevOps runs SAST\u002FDAST automatically the moment you turn it on. But a scan 'running' and a vulnerable image never reaching your production K3s cluster are two completely different things. Here's how to build an audit gate between CI\u002FCD and the cluster.","2026-08-17",[1696,1684,1682,1683,1697,1698],"k3s","devsecops","admission-controller",{"path":1700,"title":1701,"description":1702,"date":1673,"tags":1703},"\u002Fblog\u002Fen\u002Fargocd-gitops-kubernetes-guide","Getting Started with ArgoCD GitOps: Practical Kubernetes Declarative Deployment","A practical guide to GitOps-based Kubernetes declarative deployment with ArgoCD. Covers repository structure, Sync Waves, ApplicationSets, and multi-cluster management.",[1704,1705,1684,1683,1687,1706],"argocd","gitops","declarative-management",{"path":1708,"title":1709,"description":1710,"date":1673,"tags":1711},"\u002Fblog\u002Fen\u002Fgithub-actions-kubernetes-cicd","Building a Kubernetes ci-cd Pipeline with GitHub Actions","Learn how to build a Kubernetes ci-cd pipeline with GitHub Actions. Covers Helm deployments, ARC auto-scaling, security scanning, and production best practices.",[1712,1684,1683,87,1685,1713],"github-actions","automation",{"path":1715,"title":1716,"description":1717,"date":1718,"tags":1719},"\u002Fblog\u002Fen\u002Fcicd-pipeline-devsecops-gate-design-four-layers","Still Bolting Security Checks Onto Your CI\u002FCD Pipeline Without Deciding Where They Belong? A Four-Layer Model for DevSecOps Gate Design","You've adopted SAST, DAST, and container scanning — yet incidents still slip into production. This article reframes CI\u002FCD pipeline security as a four-layer design problem and walks through concrete implementation patterns for K3s\u002FKubernetes environments.","2026-08-28",[1696,1684,1683,1697,1720,1705],"admission-control",{"path":1722,"title":1723,"description":1724,"date":1725,"tags":1726},"\u002Fblog\u002Fen\u002Fkubernetes-image-signing-sigstore-supply-chain","Anyone Can Rewrite an Image Tag. Why Kubernetes Needs Sigstore-Backed Signing to Prove Provenance","Container image signing explained: tags can be overwritten by anyone, and passing CI tests doesn't guarantee the image running in production is the one you built. Learn how Sigstore and Kyverno work together to reject unsigned images on Kubernetes\u002FK3s, integrated into a GitOps workflow.","2026-08-06",[1696,1684,1683,1705,1727],"security",{"path":1729,"title":1730,"description":1731,"date":1732,"tags":1733},"\u002Fblog\u002Fen\u002Fqa-to-devops-kubernetes-career-transition","A QA Engineer's 'Instinct to Break Things' Transfers Directly to Kubernetes Operations: The Fastest Path from Test Automation to a DevOps Career","The quality-gate mindset and test automation skills QA engineers already have map directly onto Kubernetes operations aptitude. Here's a realistic six-month roadmap for making the switch, and how to clear the biggest obstacle in the way.","2026-07-19",[1684,1696,1734,1683,1735],"devops","career",1792488900250]