[{"data":1,"prerenderedAt":1945},["ShallowReactive",2],{"/blog/multi-account-aws-sam-deployments-with-gitlab-ci":3,"navigation-en-us":1163,"banner-en-us":1587,"footer-en-us":1597,"blog-post-authors-en-us-Forrest Brazeal":1841,"blog-related-posts-en-us-multi-account-aws-sam-deployments-with-gitlab-ci":1855,"blog-promotions-en-us":1881,"next-steps-en-us":1935},{"id":4,"title":5,"authors":6,"body":8,"category":1138,"date":1139,"description":1140,"extension":1141,"externalUrl":1142,"faq":1142,"featured":1143,"heroImage":1144,"meta":1145,"navigation":1146,"path":1147,"seo":1148,"slug":1152,"stem":1153,"tags":1154,"template":1161,"updatedDate":1142,"__hash__":1162},"blogPosts/en-us/blog/multi-account-aws-sam-deployments-with-gitlab-ci.md","How to set up multi-account AWS SAM deployments with GitLab CI/CD",[7],"Forrest Brazeal",{"type":9,"value":10,"toc":1118},"minimark",[11,21,30,33,41,60,74,79,82,96,101,109,113,116,170,191,204,217,221,229,234,237,244,255,259,265,269,272,279,352,358,361,370,375,381,386,393,462,465,469,476,483,487,494,607,636,643,647,650,661,672,676,681,689,692,705,828,847,853,862,976,987,990,994,999,1002,1008,1011,1015,1018,1029,1033,1036,1040,1056,1059,1082,1091,1095,1114],[12,13,14,15,20],"p",{},"I've been working with ",[16,17,19],"a",{"href":18},"/topics/serverless/","serverless"," applications in AWS for about three years – that makes me an old salt in serverless terms! So I know that deploying and maintaining a serverless app can be tricky; the tooling often has critical gaps.",[12,22,23,24,29],{},"AWS's ",[16,25,28],{"href":26,"rel":27},"https://aws.amazon.com/serverless/sam/",[],"SAM (Serverless Application Model)"," is an open source framework that makes it easier to define AWS resources – such as Lambda functions, API Gateway APIs and DynamoDB tables – commonly used in serverless applications. Once you lay out your app in a SAM template, the next thing you need is a consistent, repeatable way to get that template off your laptop and deployed in the cloud.",[12,31,32],{},"You need CI/CD.",[12,34,35,36,40],{},"I've used several different ",[16,37,39],{"href":38},"/topics/ci-cd/","CI/CD systems"," to automate SAM deployments, and I always look for the following features:",[42,43,44,48,51,54,57],"ul",{},[45,46,47],"li",{},"A single deployment pipeline that can build once and securely deploy to multiple AWS accounts (dev, staging, prod).",[45,49,50],{},"Dynamic feature branch deployments, so serverless devs can collaborate in the cloud without stepping on each other.",[45,52,53],{},"Automated cleanup of feature deployments.",[45,55,56],{},"Review of our SAM application directly integrated with the CI/CD tool's user interface.",[45,58,59],{},"Manual confirmation before code is released into production.",[12,61,62,63,67,68,73],{},"In this post, we'll find out how ",[16,64,66],{"href":65},"/solutions/continuous-integration/","GitLab CI"," can check these boxes on its way to delivering effective CI/CD for AWS SAM. You can follow along using ",[16,69,72],{"href":70,"rel":71},"https://gitlab.com/gitlab-examples/aws-sam",[],"the official example code, available here",".",[75,76,78],"h2",{"id":77},"multi-account-aws-deployments","Multi-account AWS deployments",[12,80,81],{},"We'll want to set up our deployment pipeline across multiple AWS accounts, because accounts are the only true security boundary in AWS. We don't want to run any risk of deploying prod data in dev, or vice versa. Our multi-account setup will look something like this:",[12,83,84,85,90,91,95],{},"Any time we work with multiple AWS accounts, we need cross-account ",[16,86,89],{"href":87,"rel":88},"https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html",[],"IAM roles"," in order to authorize deployments. We'll handle this task through the following steps. (All referenced scripts are available in the ",[16,92,94],{"href":70,"rel":93},[],"example repo",")",[97,98,100],"h3",{"id":99},"_1-establish-three-aws-accounts-for-development-staging-and-production-deployments","1. Establish three AWS accounts for development, staging, and production deployments",[12,102,103,104,73],{},"You can use existing AWS accounts if you have them, or ",[16,105,108],{"href":106,"rel":107},"https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_accounts_create.html",[],"provision new ones under an AWS Organization",[97,110,112],{"id":111},"_2-set-up-gitlab-iam-roles-in-each-account","2. Set up GitLab IAM roles in each account",[12,114,115],{},"Run the following AWS CLI call with admin credentials in each of the three accounts:",[117,118,123],"pre",{"className":119,"code":120,"language":121,"meta":122,"style":122},"language-shell shiki shiki-themes github-light","aws cloudformation deploy --stack-name GitLabCIRoles --template-file setup-templates/roles.yml --capabilities CAPABILITY_NAMED_IAM --parameter-overrides CIAccountID=\"\u003CAWS Account ID where your GitLab CI/CD runner lives>\" CIAccountSTSCondition=\"\u003CThe aws:userid for the IAM principal used by the Gitlab runner>\"\n","shell","",[124,125,126],"code",{"__ignoreMap":122},[127,128,131,135,139,142,146,149,152,155,158,161,164,167],"span",{"class":129,"line":130},"line",1,[127,132,134],{"class":133},"s7eDp","aws",[127,136,138],{"class":137},"sYBdl"," cloudformation",[127,140,141],{"class":137}," deploy",[127,143,145],{"class":144},"sYu0t"," --stack-name",[127,147,148],{"class":137}," GitLabCIRoles",[127,150,151],{"class":144}," --template-file",[127,153,154],{"class":137}," setup-templates/roles.yml",[127,156,157],{"class":144}," --capabilities",[127,159,160],{"class":137}," CAPABILITY_NAMED_IAM",[127,162,163],{"class":144}," --parameter-overrides",[127,165,166],{"class":137}," CIAccountID=\"\u003CAWS Account ID where your GitLab CI/CD runner lives>\"",[127,168,169],{"class":137}," CIAccountSTSCondition=\"\u003CThe aws:userid for the IAM principal used by the Gitlab runner>\"\n",[12,171,172,173,176,177,180,181,184,185,190],{},"Replace ",[124,174,175],{},"CIAccountID"," and ",[124,178,179],{},"CIAccountSTSCondition"," as indicated with values from the AWS account where your GitLab CI/CD runner exists. (Need help finding the ",[124,182,183],{},"aws:userid"," for your runner’s IAM principal? Check out ",[16,186,189],{"href":187,"rel":188},"https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_variables.html#principaltable",[],"this guide",".)",[12,192,193,194,176,197,200,201,203],{},"This CloudFormation template defines two roles: ",[124,195,196],{},"SharedServiceRole",[124,198,199],{},"SharedDeploymentRole",". The ",[124,202,196],{}," is assumed by the GitLab CI/CD runner when calling the AWS CloudFormation service. This role trusts the GitLab CI/CD runner's role. It has permissions to call the CloudFormation service, pass a role via IAM, and access S3 and CloudFront: nothing else. This role is not privileged enough to do arbitrary AWS deployments on its own.",[12,205,206,207,209,210,213,214,216],{},"The ",[124,208,199],{},", on the other hand, has full administrative access to perform any AWS action. A such, it cannot be assumed directly by the GitLab CI/CD runner. Instead, this role must be \"passed\" to CloudFormation using the service's ",[124,211,212],{},"RoleArn"," parameter. The CloudFormation service trusts the ",[124,215,199],{}," and can use it to deploy whatever resources are needed as part of the pipeline.",[97,218,220],{"id":219},"_3-create-an-s3-bucket-for-ci-artifacts","3. Create an S3 bucket for CI artifacts",[12,222,223,224,228],{},"Grab the AWS account ID for each of your development, staging, and production accounts, then deploy this CloudFormation template ",[225,226,227],"strong",{},"in the account where your GitLab CI/CD Runner exists",":",[12,230,231],{},[124,232,233],{},"aws cloudformation deploy --stack-name GitLabCIBucket --template-file setup-templates/ci-bucket.yml --parameter-overrides DevAwsAccountId=\"\u003CAWS Account ID for dev>\" StagingAwsAccountId=\"\u003CAWS Account ID for staging>\" ProdAwsAccountId=\"\u003CAWS Account ID for prod>\" ArtifactBucketName=\"\u003CA unique name for your bucket>\"",[12,235,236],{},"This CloudFormation template creates a centralized S3 bucket which holds the artifacts created during your pipeline run. Artifacts are created once for each branch push and reused between staging and production. The bucket policy allows the development, test, and production accounts to reference the same artifacts when deploying CloudFormation stacks -- checking off our \"build once, deploy many\" requirement.",[97,238,240,241,243],{"id":239},"_4-assume-the-sharedservicerole-before-making-any-cross-account-aws-calls","4. Assume the ",[124,242,196],{}," before making any cross-account AWS calls",[12,245,246,247,250,251,254],{},"We have provided the script ",[124,248,249],{},"assume-role.sh",", which will assume the provided role and export temporary AWS credentials to the current shell. It is sourced in the various ",[124,252,253],{},".gitlab-ci.yml"," build scripts.",[75,256,258],{"id":257},"single-deployment-pipeline","Single deployment pipeline",[12,260,261,262,264],{},"That brings us to the ",[124,263,253],{}," file you can see at the root of our example repository. GitLab CI/CD is smart enough to dynamically create and execute the pipeline based on that template when we push code to GitLab. The file has a number of variables at the top that you can tweak based on your environment specifics.",[97,266,268],{"id":267},"stages","Stages",[12,270,271],{},"Our Gitlab CI/CD pipeline contains seven possible stages, defined as follows:",[12,273,274],{},[275,276],"img",{"alt":277,"src":278},"Multi-account AWS SAM deployment model with GitLab CI","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398741/blog/Content%20Images/multi-account-aws-sam/deployment-model.png",[117,280,284],{"className":281,"code":282,"language":283,"meta":122,"style":122},"language-yaml shiki shiki-themes github-light","stages:\n - test\n - build-dev\n - deploy-dev\n - build-staging\n - deploy-staging\n - create-change-prod\n - execute-change-prod\n\n","yaml",[124,285,286,295,304,312,320,328,336,344],{"__ignoreMap":122},[127,287,288,291],{"class":129,"line":130},[127,289,267],{"class":290},"shJU0",[127,292,294],{"class":293},"sgsFI",":\n",[127,296,298,301],{"class":129,"line":297},2,[127,299,300],{"class":293}," - ",[127,302,303],{"class":137},"test\n",[127,305,307,309],{"class":129,"line":306},3,[127,308,300],{"class":293},[127,310,311],{"class":137},"build-dev\n",[127,313,315,317],{"class":129,"line":314},4,[127,316,300],{"class":293},[127,318,319],{"class":137},"deploy-dev\n",[127,321,323,325],{"class":129,"line":322},5,[127,324,300],{"class":293},[127,326,327],{"class":137},"build-staging\n",[127,329,331,333],{"class":129,"line":330},6,[127,332,300],{"class":293},[127,334,335],{"class":137},"deploy-staging\n",[127,337,339,341],{"class":129,"line":338},7,[127,340,300],{"class":293},[127,342,343],{"class":137},"create-change-prod\n",[127,345,347,349],{"class":129,"line":346},8,[127,348,300],{"class":293},[127,350,351],{"class":137},"execute-change-prod\n",[12,353,354],{},[275,355],{"alt":356,"src":357},"Deployment lifecycle stages","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398741/blog/Content%20Images/multi-account-aws-sam/deployment-lifecycle-stages.png",[12,359,360],{},"\"Stages\" are used as a control flow mechanism when building the pipeline. Multiple build jobs within a stage will run in parallel, but all jobs in a given stage must complete before any jobs belonging to the next stage in the list can be executed.",[12,362,363,364,369],{},"Although seven stages are defined here, only certain ones will execute, depending on what kind of Git action triggered our pipeline. We effectively have three stages to any deployment: a \"test\" phase where we run unit tests and dependency scans against our code, a \"build\" phase that packages our SAM template, and a \"deploy\" phase split into two parts: creating a ",[16,365,368],{"href":366,"rel":367},"https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html",[],"CloudFormation change set"," and then executing that change set in the target environment.",[371,372,374],"h4",{"id":373},"test","Test",[12,376,377,378,380],{},"Our ",[124,379,253],{}," file currently runs two types of tests: unit tests against our code, and dependency scans against our third-party Python packages.",[382,383,385],"h5",{"id":384},"unit-tests","Unit tests",[12,387,388,389,392],{},"Unit tests run on every branch pushed to the remote repository. This behavior is defined by the ",[124,390,391],{},"only: branches"," property in the job shown below:",[117,394,396],{"className":281,"code":395,"language":283,"meta":122,"style":122},"test:unit:\n stage: test\n only:\n   - branches\n script: |\n   if test -f requirements.txt; then\n       pip install -r requirements.txt\n   fi\n   python -m pytest --ignore=functions/\n\n",[124,397,398,405,415,422,430,441,446,451,456],{"__ignoreMap":122},[127,399,400,403],{"class":129,"line":130},[127,401,402],{"class":290},"test:unit",[127,404,294],{"class":293},[127,406,407,410,413],{"class":129,"line":297},[127,408,409],{"class":290}," stage",[127,411,412],{"class":293},": ",[127,414,303],{"class":137},[127,416,417,420],{"class":129,"line":306},[127,418,419],{"class":290}," only",[127,421,294],{"class":293},[127,423,424,427],{"class":129,"line":314},[127,425,426],{"class":293},"   - ",[127,428,429],{"class":137},"branches\n",[127,431,432,435,437],{"class":129,"line":322},[127,433,434],{"class":290}," script",[127,436,412],{"class":293},[127,438,440],{"class":439},"sD7c4","|\n",[127,442,443],{"class":129,"line":330},[127,444,445],{"class":137},"   if test -f requirements.txt; then\n",[127,447,448],{"class":129,"line":338},[127,449,450],{"class":137},"       pip install -r requirements.txt\n",[127,452,453],{"class":129,"line":346},[127,454,455],{"class":137},"   fi\n",[127,457,459],{"class":129,"line":458},9,[127,460,461],{"class":137},"   python -m pytest --ignore=functions/\n",[12,463,464],{},"Every GitLab CI/CD job runs a script. Here, we install any dependencies, then execute Python unit tests.",[382,466,468],{"id":467},"dependency-scans","Dependency scans",[12,470,471,475],{},[16,472,468],{"href":473,"rel":474},"https://docs.gitlab.com/user/application_security/dependency_scanning/",[],", which can take a few minutes, run only on code pushed to the master branch; it would be counterproductive for developers to wait on them every time they want to test code.",[12,477,478,479,482],{},"These scans use a hardcoded, standard Docker image to mount the code and run \"Docker in Docker\" checks against a database of known package vulnerabilities. If a vulnerability is found, the pipeline will log the error without stopping the build (that's what the ",[124,480,481],{},"allow-failure: true"," property does).",[371,484,486],{"id":485},"build","Build",[12,488,489,490,493],{},"The build stage turns our SAM template into CloudFormation and turns our Python code into a valid AWS Lambda deployment package. For example, here's the ",[124,491,492],{},"build:dev"," job:",[117,495,497],{"className":281,"code":496,"language":283,"meta":122,"style":122},"build:dev:\n stage: build-dev\n \u003C\u003C: *build_script\n variables:\n   \u003C\u003C: *dev_variables\n artifacts:\n   paths:\n     - deployment.yml\n   expire_in: 1 week\n only:\n   - branches\n except:\n   - master\n\n",[124,498,499,505,513,526,533,545,552,559,567,577,584,591,599],{"__ignoreMap":122},[127,500,501,503],{"class":129,"line":130},[127,502,492],{"class":290},[127,504,294],{"class":293},[127,506,507,509,511],{"class":129,"line":297},[127,508,409],{"class":290},[127,510,412],{"class":293},[127,512,311],{"class":137},[127,514,515,518,520,523],{"class":129,"line":306},[127,516,517],{"class":144}," \u003C\u003C",[127,519,412],{"class":293},[127,521,522],{"class":439},"*",[127,524,525],{"class":293},"build_script\n",[127,527,528,531],{"class":129,"line":314},[127,529,530],{"class":290}," variables",[127,532,294],{"class":293},[127,534,535,538,540,542],{"class":129,"line":322},[127,536,537],{"class":144},"   \u003C\u003C",[127,539,412],{"class":293},[127,541,522],{"class":439},[127,543,544],{"class":293},"dev_variables\n",[127,546,547,550],{"class":129,"line":330},[127,548,549],{"class":290}," artifacts",[127,551,294],{"class":293},[127,553,554,557],{"class":129,"line":338},[127,555,556],{"class":290},"   paths",[127,558,294],{"class":293},[127,560,561,564],{"class":129,"line":346},[127,562,563],{"class":293},"     - ",[127,565,566],{"class":137},"deployment.yml\n",[127,568,569,572,574],{"class":129,"line":458},[127,570,571],{"class":290},"   expire_in",[127,573,412],{"class":293},[127,575,576],{"class":137},"1 week\n",[127,578,580,582],{"class":129,"line":579},10,[127,581,419],{"class":290},[127,583,294],{"class":293},[127,585,587,589],{"class":129,"line":586},11,[127,588,426],{"class":293},[127,590,429],{"class":137},[127,592,594,597],{"class":129,"line":593},12,[127,595,596],{"class":290}," except",[127,598,294],{"class":293},[127,600,602,604],{"class":129,"line":601},13,[127,603,426],{"class":293},[127,605,606],{"class":137},"master\n",[12,608,609,610,176,613,616,617,620,621,624,625,627,628,631,632,635],{},"What's going on here? Note first the combination of ",[124,611,612],{},"only",[124,614,615],{},"except"," properties to ensure that our development builds happen only on pushes to branches that aren't ",[124,618,619],{},"master",". We're referring to ",[124,622,623],{},"dev_variables",", the set of development-specific variables defined at the top of ",[124,626,253],{},". And we're running a script, pointed to by ",[124,629,630],{},"build_script",", which packages our SAM template and code for deployment using the ",[124,633,634],{},"aws cloudformation package"," CLI call.",[12,637,638,639,642],{},"The artifact ",[124,640,641],{},"deployment.yml"," is the CloudFormation template output by our package command. It has all the implicit SAM magic expanded into CloudFormation resources. By managing it as an artifact, we can pass it along to further steps in the build pipeline, even though it isn't committed to our repository.",[371,644,646],{"id":645},"deploy","Deploy",[12,648,649],{},"Our deployments use AWS CloudFormation to deploy the packaged application in a target AWS environment.",[12,651,652,653,656,657,660],{},"In development and staging environments, we use the ",[124,654,655],{},"aws cloudformation deploy"," command to create a change set and immediately execute it. In production, we put a manual \"wait\" in the pipeline at this point so you have the opportunity to review the change set before moving onto the \"Execute\" step, which actually calls ",[124,658,659],{},"aws cloudformation execute-changeset"," to update the underlying stack.",[12,662,663,664,667,668,671],{},"Our deployment jobs use a helper script, committed to the top level of the example repository, called ",[124,665,666],{},"cfn-wait.sh",". This script is needed because the ",[124,669,670],{},"aws cloudformation"," commands don't wait for results; they report success as soon as the stack operation starts. To properly record the deployment results in our job, we need a script that polls the CloudFormation service and throws an error if the deployment or update fails.",[75,673,675],{"id":674},"dynamic-feature-branch-deployments-and-review-apps","Dynamic feature branch deployments and Review Apps",[12,677,678],{},[275,679],{"alt":675,"src":680},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398741/blog/Content%20Images/multi-account-aws-sam/dynamic-feature-branch-deployments.png",[12,682,683,684,688],{},"When a non-master branch is pushed to GitLab, our pipeline runs tests, builds the ",[16,685,687],{"href":686},"/solutions/source-code-management/","updated source code",", and deploys and/or updates the changed CloudFormation resources in the development AWS account. When the branch is merged into master, or if someone clicks the \"Stop\" button next to the branch's environment in GitLab CI, the CloudFormation stack will be torn down automatically.",[12,690,691],{},"It is perfectly possible, and indeed desirable, to have multiple development feature branches simultaneously deployed as live environments for more efficient parallel feature development and QA. The serverless model makes this a cost-effective strategy for collaborating in the cloud.",[12,693,694,695,700,701,704],{},"If we are dynamically deploying our application on every branch push, we might like to view it as part of our interaction with the GitLab console (such as during a code review). GitLab supports this with a nifty feature called ",[16,696,699],{"href":697,"rel":698},"https://docs.gitlab.com/ci/review_apps/",[],"Review Apps",". Review Apps allow you to specify an \"environment\" as part of a deployment job, as seen in our ",[124,702,703],{},"deploy:dev"," job below:",[117,706,708],{"className":281,"code":707,"language":283,"meta":122,"style":122},"deploy:dev:\n \u003C\u003C: *deploy_script\n stage: deploy-dev\n dependencies:\n   - build:dev\n variables:\n   \u003C\u003C: *dev_variables\n environment:\n   name: review/$CI_COMMIT_REF_NAME\n   url: https://${CI_COMMIT_REF_NAME}.${DEV_HOSTED_ZONE_NAME}/services\n   on_stop: stop:dev\n only:\n   - branches\n except:\n   - master\n\n",[124,709,710,716,727,735,742,749,755,765,772,782,792,802,808,814,821],{"__ignoreMap":122},[127,711,712,714],{"class":129,"line":130},[127,713,703],{"class":290},[127,715,294],{"class":293},[127,717,718,720,722,724],{"class":129,"line":297},[127,719,517],{"class":144},[127,721,412],{"class":293},[127,723,522],{"class":439},[127,725,726],{"class":293},"deploy_script\n",[127,728,729,731,733],{"class":129,"line":306},[127,730,409],{"class":290},[127,732,412],{"class":293},[127,734,319],{"class":137},[127,736,737,740],{"class":129,"line":314},[127,738,739],{"class":290}," dependencies",[127,741,294],{"class":293},[127,743,744,746],{"class":129,"line":322},[127,745,426],{"class":293},[127,747,748],{"class":137},"build:dev\n",[127,750,751,753],{"class":129,"line":330},[127,752,530],{"class":290},[127,754,294],{"class":293},[127,756,757,759,761,763],{"class":129,"line":338},[127,758,537],{"class":144},[127,760,412],{"class":293},[127,762,522],{"class":439},[127,764,544],{"class":293},[127,766,767,770],{"class":129,"line":346},[127,768,769],{"class":290}," environment",[127,771,294],{"class":293},[127,773,774,777,779],{"class":129,"line":458},[127,775,776],{"class":290},"   name",[127,778,412],{"class":293},[127,780,781],{"class":137},"review/$CI_COMMIT_REF_NAME\n",[127,783,784,787,789],{"class":129,"line":579},[127,785,786],{"class":290},"   url",[127,788,412],{"class":293},[127,790,791],{"class":137},"https://${CI_COMMIT_REF_NAME}.${DEV_HOSTED_ZONE_NAME}/services\n",[127,793,794,797,799],{"class":129,"line":586},[127,795,796],{"class":290},"   on_stop",[127,798,412],{"class":293},[127,800,801],{"class":137},"stop:dev\n",[127,803,804,806],{"class":129,"line":593},[127,805,419],{"class":290},[127,807,294],{"class":293},[127,809,810,812],{"class":129,"line":601},[127,811,426],{"class":293},[127,813,429],{"class":137},[127,815,817,819],{"class":129,"line":816},14,[127,818,596],{"class":290},[127,820,294],{"class":293},[127,822,824,826],{"class":129,"line":823},15,[127,825,426],{"class":293},[127,827,606],{"class":137},[12,829,830,831,834,835,838,839,842,843,846],{},"The link specified in the ",[124,832,833],{},"url"," field of the ",[124,836,837],{},"environment"," property will be accessible in the ",[124,840,841],{},"Environments"," section of GitLab CI/CD or on any merge request of the associated branch. (In the case of the sample SAM application provided with our example, since we don't have a front end to view, the link just takes you to a GET request for the ",[124,844,845],{},"/services"," API endpoint and should display some raw JSON in your browser.)",[12,848,849],{},[275,850],{"alt":851,"src":852},"Link to live environment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398741/blog/Content%20Images/multi-account-aws-sam/link-live-environment.png",[12,854,206,855,858,859,228],{},[124,856,857],{},"on_stop"," property specifies what happens when you \"shut down\" the environment in GitLab CI. This can be done manually or by deleting the associated branch. In the case above, we have stopped behavior for dev environments linked to a separate job called ",[124,860,861],{},"stop:dev",[117,863,865],{"className":281,"code":864,"language":283,"meta":122,"style":122},"stop:dev:\n stage: deploy-dev\n variables:\n   GIT_STRATEGY: none\n   \u003C\u003C: *dev_variables\n \u003C\u003C: *shutdown_script\n when: manual\n environment:\n   name: review/$CI_COMMIT_REF_NAME\n   action: stop\n only:\n   - branches\n except:\n   - master\n\n",[124,866,867,873,881,887,897,907,918,928,934,942,952,958,964,970],{"__ignoreMap":122},[127,868,869,871],{"class":129,"line":130},[127,870,861],{"class":290},[127,872,294],{"class":293},[127,874,875,877,879],{"class":129,"line":297},[127,876,409],{"class":290},[127,878,412],{"class":293},[127,880,319],{"class":137},[127,882,883,885],{"class":129,"line":306},[127,884,530],{"class":290},[127,886,294],{"class":293},[127,888,889,892,894],{"class":129,"line":314},[127,890,891],{"class":290},"   GIT_STRATEGY",[127,893,412],{"class":293},[127,895,896],{"class":137},"none\n",[127,898,899,901,903,905],{"class":129,"line":322},[127,900,537],{"class":144},[127,902,412],{"class":293},[127,904,522],{"class":439},[127,906,544],{"class":293},[127,908,909,911,913,915],{"class":129,"line":330},[127,910,517],{"class":144},[127,912,412],{"class":293},[127,914,522],{"class":439},[127,916,917],{"class":293},"shutdown_script\n",[127,919,920,923,925],{"class":129,"line":338},[127,921,922],{"class":290}," when",[127,924,412],{"class":293},[127,926,927],{"class":137},"manual\n",[127,929,930,932],{"class":129,"line":346},[127,931,769],{"class":290},[127,933,294],{"class":293},[127,935,936,938,940],{"class":129,"line":458},[127,937,776],{"class":290},[127,939,412],{"class":293},[127,941,781],{"class":137},[127,943,944,947,949],{"class":129,"line":579},[127,945,946],{"class":290},"   action",[127,948,412],{"class":293},[127,950,951],{"class":137},"stop\n",[127,953,954,956],{"class":129,"line":586},[127,955,419],{"class":290},[127,957,294],{"class":293},[127,959,960,962],{"class":129,"line":593},[127,961,426],{"class":293},[127,963,429],{"class":137},[127,965,966,968],{"class":129,"line":601},[127,967,596],{"class":290},[127,969,294],{"class":293},[127,971,972,974],{"class":129,"line":816},[127,973,426],{"class":293},[127,975,606],{"class":137},[12,977,978,979,982,983,986],{},"This job launches the ",[124,980,981],{},"shutdown_script"," script, which calls ",[124,984,985],{},"aws cloudformation teardown"," to clean up the SAM deployment.",[12,988,989],{},"For safety's sake, there is no automated teardown of staging or production environments.",[75,991,993],{"id":992},"production-releases","Production releases",[12,995,996],{},[275,997],{"alt":993,"src":998},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398742/blog/Content%20Images/multi-account-aws-sam/production-releases.png",[12,1000,1001],{},"When a change is merged into the master branch, the code is built, tested (including dependency scans) and deployed to the staging environment. This is a separate, stable environment that developers, QA, and others can use to verify changes before attempting to deploy in production.",[12,1003,1004],{},[275,1005],{"alt":1006,"src":1007},"Staging environment","https://res.cloudinary.com/about-gitlab-com/image/upload/v1782398742/blog/Content%20Images/multi-account-aws-sam/staging-environment.png",[12,1009,1010],{},"After deploying code to the staging environment, the pipeline will create a change set for the production stack, and then pause for a manual intervention. A human user must click a button in the Gitlab CI/CD \"Environments\" view to execute the final change set.",[75,1012,1014],{"id":1013},"now-what","Now what?",[12,1016,1017],{},"Step back and take a deep breath – that was a lot of information! Let's not lose sight of what we've done here: we've defined a secure, multi-account AWS deployment pipeline in our GitLab repo, integrated tests, builds and deployments, and successfully rolled a SAM-defined serverless app to the cloud. Not bad for a few lines of config!",[12,1019,1020,1021,1024,1025,1028],{},"The next step is to try this on your own. If you'd like to start with our sample \"AWS News\" application, you can simply run ",[124,1022,1023],{},"sam init --location git+https://gitlab.com/gitlab-examples/aws-sam"," to download the project on your local machine. The AWS News app contains a stripped-down, single-account version of the ",[124,1026,1027],{},"gitlab-ci.yml"," file discussed in this post, so you can try out deployments with minimal setup needed.",[75,1030,1032],{"id":1031},"further-reading","Further reading",[12,1034,1035],{},"We have barely scratched the surface of GitLab CI/CD and AWS SAM in this post. Here are some interesting readings if you would like to take your work to the next level:",[97,1037,1039],{"id":1038},"sam","SAM",[42,1041,1042,1049],{},[45,1043,1044],{},[16,1045,1048],{"href":1046,"rel":1047},"https://aws.amazon.com/blogs/compute/implementing-safe-aws-lambda-deployments-with-aws-codedeploy/",[],"Implementing safe AWS Lambda deployments with AWS SAM and CodeDeploy",[45,1050,1051],{},[16,1052,1055],{"href":1053,"rel":1054},"https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-test-and-debug.html",[],"Running and debugging serverless applications locally using the AWS SAM CLI",[97,1057,66],{"id":1058},"gitlab-ci",[42,1060,1061,1068,1075],{},[45,1062,1063],{},[16,1064,1067],{"href":1065,"rel":1066},"https://hackernoon.com/configuring-gitlab-ci-on-aws-ec2-using-docker-7c359d513a46",[],"Setting up a GitLab Runner on EC2",[45,1069,1070],{},[16,1071,1074],{"href":1072,"rel":1073},"https://docs.gitlab.com/ci/pipelines/schedules/",[],"Scheduled pipelines",[45,1076,1077],{},[16,1078,1081],{"href":1079,"rel":1080},"https://docs.gitlab.com/ci/chatops/",[],"ChatOps",[12,1083,1084,1085,1090],{},"Please ",[16,1086,1089],{"href":1087,"rel":1088},"https://twitter.com/forrestbrazeal",[],"let me know"," if you have further questions!",[97,1092,1094],{"id":1093},"about-the-guest-author","About the guest author",[12,1096,1097,1098,1103,1104,1109,1110,73],{},"Forrest Brazeal is an ",[16,1099,1102],{"href":1100,"rel":1101},"https://aws.amazon.com/developer/community/heroes/forrest-brazeal/",[],"AWS Serverless Hero",". He currently works as a senior cloud architect at ",[16,1105,1108],{"href":1106,"rel":1107},"https://trek10.com",[],"Trek10",", an AWS Advanced Consulting Partner. You can ",[16,1111,1113],{"href":1112},"/customers/trek10/","read more about Trek10's GitLab journey here",[1115,1116,1117],"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}html pre.shiki code .sD7c4, html code.shiki .sD7c4{--shiki-default:#D73A49}",{"title":122,"searchDepth":297,"depth":297,"links":1119},[1120,1127,1130,1131,1132,1133],{"id":77,"depth":297,"text":78,"children":1121},[1122,1123,1124,1125],{"id":99,"depth":306,"text":100},{"id":111,"depth":306,"text":112},{"id":219,"depth":306,"text":220},{"id":239,"depth":306,"text":1126},"4. Assume the SharedServiceRole before making any cross-account AWS calls",{"id":257,"depth":297,"text":258,"children":1128},[1129],{"id":267,"depth":306,"text":268},{"id":674,"depth":297,"text":675},{"id":992,"depth":297,"text":993},{"id":1013,"depth":297,"text":1014},{"id":1031,"depth":297,"text":1032,"children":1134},[1135,1136,1137],{"id":1038,"depth":306,"text":1039},{"id":1058,"depth":306,"text":66},{"id":1093,"depth":306,"text":1094},"engineering","2019-02-04","Our guest author, an AWS Serverless hero, shares how to automate SAM deployments using GitLab CI/CD.","md",null,false,"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749666959/Blog/Hero%20Images/gitlab-aws-cover.png",{},true,"/en-us/blog/multi-account-aws-sam-deployments-with-gitlab-ci",{"title":5,"description":1140,"ogTitle":5,"ogDescription":1140,"noIndex":1143,"ogImage":1144,"ogUrl":1149,"ogSiteName":1150,"ogType":1151,"canonicalUrls":1149},"https://about.gitlab.com/blog/multi-account-aws-sam-deployments-with-gitlab-ci","https://about.gitlab.com","article","multi-account-aws-sam-deployments-with-gitlab-ci","en-us/blog/multi-account-aws-sam-deployments-with-gitlab-ci",[1155,1156,1157,1158,1159,1160],"CI/CD","demo","integrations","open source","production","user stories","BlogPost","g8PmFadq9_IZiJWHpiGbKmXladTMpJd7so9M0yBKnkg",{"logo":1164,"freeTrial":1169,"sales":1174,"login":1179,"items":1184,"search":1507,"minimal":1538,"duo":1557,"switchNav":1566,"pricingDeployment":1577},{"config":1165},{"href":1166,"dataGaName":1167,"dataGaLocation":1168},"/","gitlab logo","header",{"text":1170,"config":1171},"Get free trial",{"href":1172,"dataGaName":1173,"dataGaLocation":1168},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":1175,"config":1176},"Request a demo",{"href":1177,"dataGaName":1178,"dataGaLocation":1168},"/sales/?contact-topic=request-demo","sales",{"text":1180,"config":1181},"Sign in",{"href":1182,"dataGaName":1183,"dataGaLocation":1168},"https://gitlab.com/users/sign_in/","sign in",[1185,1214,1311,1316,1429,1485],{"text":1186,"config":1187,"menu":1189},"Platform",{"dataNavLevelOne":1188},"platform",{"type":1190,"columns":1191},"cards",[1192,1198,1206],{"title":1186,"description":1193,"link":1194},"The intelligent orchestration platform for DevSecOps",{"text":1195,"config":1196},"Explore our Platform",{"href":1197,"dataGaName":1188,"dataGaLocation":1168},"/platform/",{"title":1199,"description":1200,"link":1201},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":1202,"config":1203},"Meet GitLab Duo",{"href":1204,"dataGaName":1205,"dataGaLocation":1168},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":1207,"description":1208,"link":1209},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":1210,"config":1211},"Learn more",{"href":1212,"dataGaName":1213,"dataGaLocation":1168},"/why-gitlab/","why gitlab",{"text":1215,"left":1146,"config":1216,"menu":1218},"Product",{"dataNavLevelOne":1217},"solutions",{"type":1219,"link":1220,"columns":1224,"feature":1290},"lists",{"text":1221,"config":1222},"View all Solutions",{"href":1223,"dataGaName":1217,"dataGaLocation":1168},"/solutions/",[1225,1246,1269],{"title":1226,"description":1227,"link":1228,"items":1233},"Automation","CI/CD and automation to accelerate deployment",{"config":1229},{"icon":1230,"href":1231,"dataGaName":1232,"dataGaLocation":1168},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[1234,1236,1239,1242],{"text":1155,"config":1235},{"href":65,"dataGaLocation":1168,"dataGaName":1155},{"text":1199,"config":1237},{"href":1204,"dataGaLocation":1168,"dataGaName":1238},"gitlab duo agent platform - product menu",{"text":1240,"config":1241},"Source Code Management",{"href":686,"dataGaLocation":1168,"dataGaName":1240},{"text":1243,"config":1244},"Automated Software Delivery",{"href":1231,"dataGaLocation":1168,"dataGaName":1245},"Automated software delivery",{"title":1247,"description":1248,"link":1249,"items":1254},"Security","Deliver code faster without compromising security",{"config":1250},{"href":1251,"dataGaName":1252,"dataGaLocation":1168,"icon":1253},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[1255,1259,1264],{"text":1256,"config":1257},"Application Security Testing",{"href":1251,"dataGaName":1258,"dataGaLocation":1168},"Application security testing",{"text":1260,"config":1261},"Software Supply Chain Security",{"href":1262,"dataGaLocation":1168,"dataGaName":1263},"/solutions/supply-chain/","Software supply chain security",{"text":1265,"config":1266},"Software Compliance",{"href":1267,"dataGaName":1268,"dataGaLocation":1168},"/solutions/software-compliance/","software compliance",{"title":1270,"link":1271,"items":1276},"Measurement",{"config":1272},{"icon":1273,"href":1274,"dataGaName":1275,"dataGaLocation":1168},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[1277,1281,1285],{"text":1278,"config":1279},"Visibility & Measurement",{"href":1274,"dataGaLocation":1168,"dataGaName":1280},"Visibility and Measurement",{"text":1282,"config":1283},"Value Stream Management",{"href":1284,"dataGaLocation":1168,"dataGaName":1282},"/solutions/value-stream-management/",{"text":1286,"config":1287},"Analytics & Insights",{"href":1288,"dataGaLocation":1168,"dataGaName":1289},"/solutions/analytics-and-insights/","Analytics and insights",{"title":1291,"type":1219,"items":1292},"GitLab for",[1293,1299,1305],{"text":1294,"config":1295},"Enterprise",{"icon":1296,"href":1297,"dataGaLocation":1168,"dataGaName":1298},"Building","/enterprise/","enterprise",{"text":1300,"config":1301},"Small Business",{"icon":1302,"href":1303,"dataGaLocation":1168,"dataGaName":1304},"Work","/small-business/","small business",{"text":1306,"config":1307},"Public Sector",{"icon":1308,"href":1309,"dataGaLocation":1168,"dataGaName":1310},"Organization","/solutions/public-sector/","public sector",{"text":1312,"config":1313},"Pricing",{"href":1314,"dataGaName":1315,"dataGaLocation":1168,"dataNavLevelOne":1315},"/pricing/","pricing",{"text":1317,"config":1318,"menu":1320},"Resources",{"dataNavLevelOne":1319},"resources",{"type":1219,"link":1321,"columns":1325,"feature":1418},{"text":1322,"config":1323},"View all resources",{"href":1324,"dataGaName":1319,"dataGaLocation":1168},"/resources/",[1326,1358,1385],{"title":1327,"items":1328},"Getting started",[1329,1334,1339,1344,1349,1354],{"text":1330,"config":1331},"Install",{"href":1332,"dataGaName":1333,"dataGaLocation":1168},"/install/","install",{"text":1335,"config":1336},"Quick start guides",{"href":1337,"dataGaName":1338,"dataGaLocation":1168},"/get-started/","quick setup checklists",{"text":1340,"config":1341},"Learn",{"href":1342,"dataGaLocation":1168,"dataGaName":1343},"https://university.gitlab.com/","learn",{"text":1345,"config":1346},"Product documentation",{"href":1347,"dataGaName":1348,"dataGaLocation":1168},"https://docs.gitlab.com/","product documentation",{"text":1350,"config":1351},"Best practice videos",{"href":1352,"dataGaName":1353,"dataGaLocation":1168},"/getting-started-videos/","best practice videos",{"text":1355,"config":1356},"Integrations",{"href":1357,"dataGaName":1157,"dataGaLocation":1168},"/integrations/",{"title":1359,"items":1360},"Discover",[1361,1366,1371,1376,1380],{"text":1362,"config":1363},"Customer success stories",{"href":1364,"dataGaName":1365,"dataGaLocation":1168},"/customers/","customer success stories",{"text":1367,"config":1368},"Blog",{"href":1369,"dataGaName":1370,"dataGaLocation":1168},"/blog/","blog",{"text":1372,"config":1373},"Demo Hub",{"href":1374,"dataGaName":1375,"dataGaLocation":1168},"/demo-hub/","demo hub",{"text":1377,"config":1378},"The Source",{"href":1379,"dataGaName":1370,"dataGaLocation":1168},"/the-source/",{"text":1381,"config":1382},"Remote",{"href":1383,"dataGaName":1384,"dataGaLocation":1168},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":1386,"items":1387},"Connect",[1388,1393,1398,1403,1408,1413],{"text":1389,"config":1390},"GitLab Services",{"href":1391,"dataGaName":1392,"dataGaLocation":1168},"/services/","services",{"text":1394,"config":1395},"Contribute",{"href":1396,"dataGaName":1397,"dataGaLocation":1168},"https://contributors.gitlab.com","contribute",{"text":1399,"config":1400},"Community",{"href":1401,"dataGaName":1402,"dataGaLocation":1168},"/community/","community",{"text":1404,"config":1405},"Forum",{"href":1406,"dataGaName":1407,"dataGaLocation":1168},"https://forum.gitlab.com/","forum",{"text":1409,"config":1410},"Events",{"href":1411,"dataGaName":1412,"dataGaLocation":1168},"/events/","events",{"text":1414,"config":1415},"Partners",{"href":1416,"dataGaName":1417,"dataGaLocation":1168},"/partners/","partners",{"config":1419,"title":1422,"text":1423,"link":1424},{"background":1420,"textColor":1421},"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":1425,"config":1426},"Read the latest",{"href":1427,"dataGaName":1428,"dataGaLocation":1168},"/whats-new/","whats new",{"text":1430,"config":1431,"menu":1433},"Company",{"dataNavLevelOne":1432},"company",{"type":1219,"columns":1434},[1435],{"items":1436},[1437,1442,1448,1450,1455,1460,1465,1470,1475,1480],{"text":1438,"config":1439},"About",{"href":1440,"dataGaName":1441,"dataGaLocation":1168},"/company/","about",{"text":1443,"config":1444,"footerGa":1447},"Jobs",{"href":1445,"dataGaName":1446,"dataGaLocation":1168},"/jobs/","jobs",{"dataGaName":1446},{"text":1409,"config":1449},{"href":1411,"dataGaName":1412,"dataGaLocation":1168},{"text":1451,"config":1452},"Leadership",{"href":1453,"dataGaName":1454,"dataGaLocation":1168},"/company/team/e-group/","leadership",{"text":1456,"config":1457},"Handbook",{"href":1458,"dataGaName":1459,"dataGaLocation":1168},"https://handbook.gitlab.com/","handbook",{"text":1461,"config":1462},"Investor relations",{"href":1463,"dataGaName":1464,"dataGaLocation":1168},"https://ir.gitlab.com/overview/default.aspx","investor relations",{"text":1466,"config":1467},"Trust Center",{"href":1468,"dataGaName":1469,"dataGaLocation":1168},"/security/","trust center",{"text":1471,"config":1472},"AI Transparency Center",{"href":1473,"dataGaName":1474,"dataGaLocation":1168},"/ai-transparency-center/","ai transparency center",{"text":1476,"config":1477},"Newsletter",{"href":1478,"dataGaName":1479,"dataGaLocation":1168},"/company/contact/#contact-forms","newsletter",{"text":1481,"config":1482},"Press",{"href":1483,"dataGaName":1484,"dataGaLocation":1168},"/press/","press",{"text":1486,"config":1487,"menu":1488},"Contact us",{"dataNavLevelOne":1432},{"type":1219,"columns":1489},[1490],{"items":1491},[1492,1497,1502],{"text":1493,"config":1494},"Talk to sales",{"href":1495,"dataGaName":1496,"dataGaLocation":1168},"/sales/","talk to sales",{"text":1498,"config":1499},"Support portal",{"href":1500,"dataGaName":1501,"dataGaLocation":1168},"https://support.gitlab.com/hc/en-us","support portal",{"text":1503,"config":1504},"Customer portal",{"href":1505,"dataGaName":1506,"dataGaLocation":1168},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":1508,"login":1509,"suggestions":1516},"Close",{"text":1510,"link":1511},"To search repositories and projects, login to",{"text":1512,"config":1513},"gitlab.com",{"href":1182,"dataGaName":1514,"dataGaLocation":1515},"search login","search",{"text":1517,"default":1518},"Suggestions",[1519,1521,1525,1527,1531,1535],{"text":1199,"config":1520},{"href":1204,"dataGaName":1199,"dataGaLocation":1515},{"text":1522,"config":1523},"Code Suggestions (AI)",{"href":1524,"dataGaName":1522,"dataGaLocation":1515},"/solutions/code-suggestions/",{"text":1155,"config":1526},{"href":65,"dataGaName":1155,"dataGaLocation":1515},{"text":1528,"config":1529},"GitLab on AWS",{"href":1530,"dataGaName":1528,"dataGaLocation":1515},"/partners/technology-partners/aws/",{"text":1532,"config":1533},"GitLab on Google Cloud",{"href":1534,"dataGaName":1532,"dataGaLocation":1515},"/partners/technology-partners/google-cloud-platform/",{"text":1536,"config":1537},"Why GitLab?",{"href":1212,"dataGaName":1536,"dataGaLocation":1515},{"freeTrial":1539,"mobileIcon":1544,"desktopIcon":1549,"secondaryButton":1552},{"text":1540,"config":1541},"Start free trial",{"href":1542,"dataGaName":1173,"dataGaLocation":1543},"https://gitlab.com/-/trials/new/","nav",{"altText":1545,"config":1546},"Gitlab Icon",{"src":1547,"dataGaName":1548,"dataGaLocation":1543},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":1545,"config":1550},{"src":1551,"dataGaName":1548,"dataGaLocation":1543},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":1553,"config":1554},"Get Started",{"href":1555,"dataGaName":1556,"dataGaLocation":1543},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":1558,"mobileIcon":1562,"desktopIcon":1564},{"text":1559,"config":1560},"Learn more about GitLab Duo",{"href":1204,"dataGaName":1561,"dataGaLocation":1543},"gitlab duo",{"altText":1545,"config":1563},{"src":1547,"dataGaName":1548,"dataGaLocation":1543},{"altText":1545,"config":1565},{"src":1551,"dataGaName":1548,"dataGaLocation":1543},{"button":1567,"mobileIcon":1572,"desktopIcon":1574},{"text":1568,"config":1569},"/switch",{"href":1570,"dataGaName":1571,"dataGaLocation":1543},"#contact","switch",{"altText":1545,"config":1573},{"src":1547,"dataGaName":1548,"dataGaLocation":1543},{"altText":1545,"config":1575},{"src":1576,"dataGaName":1548,"dataGaLocation":1543},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1773335277/ohhpiuoxoldryzrnhfrh.png",{"freeTrial":1578,"mobileIcon":1583,"desktopIcon":1585},{"text":1579,"config":1580},"Back to pricing",{"href":1314,"dataGaName":1581,"dataGaLocation":1543,"icon":1582},"back to pricing","GoBack",{"altText":1545,"config":1584},{"src":1547,"dataGaName":1548,"dataGaLocation":1543},{"altText":1545,"config":1586},{"src":1551,"dataGaName":1548,"dataGaLocation":1543},{"title":1588,"titleMobile":1589,"button":1590,"config":1595},"Duo Agent Platform delivers 400% ROI, per new Forrester Consulting study.","400% ROI: Forrester TEI for GitLab Duo",{"text":1210,"config":1591},{"href":1592,"dataGaName":1593,"dataGaLocation":1594},"https://about.gitlab.com/blog/gitlab-duo-agent-platform-delivers-400-percent-roi/","forrester-tei-dap-banner","global-banner",{"layout":1596,"disabled":1143},"release",{"data":1598},{"text":1599,"source":1600,"edit":1606,"contribute":1611,"config":1616,"items":1621,"minimal":1830},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":1601,"config":1602},"View page source",{"href":1603,"dataGaName":1604,"dataGaLocation":1605},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":1607,"config":1608},"Edit this page",{"href":1609,"dataGaName":1610,"dataGaLocation":1605},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":1612,"config":1613},"Please contribute",{"href":1614,"dataGaName":1615,"dataGaLocation":1605},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":1617,"facebook":1618,"youtube":1619,"linkedin":1620},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[1622,1669,1722,1766,1798],{"title":1312,"links":1623,"subMenu":1638},[1624,1628,1633],{"text":1625,"config":1626},"View plans",{"href":1314,"dataGaName":1627,"dataGaLocation":1605},"view plans",{"text":1629,"config":1630},"Why Premium?",{"href":1631,"dataGaName":1632,"dataGaLocation":1605},"/pricing/premium/","why premium",{"text":1634,"config":1635},"Why Ultimate?",{"href":1636,"dataGaName":1637,"dataGaLocation":1605},"/pricing/ultimate/","why ultimate",[1639],{"title":1640,"links":1641},"Contact Us",[1642,1645,1647,1649,1654,1659,1664],{"text":1643,"config":1644},"Contact sales",{"href":1495,"dataGaName":1178,"dataGaLocation":1605},{"text":1498,"config":1646},{"href":1500,"dataGaName":1501,"dataGaLocation":1605},{"text":1503,"config":1648},{"href":1505,"dataGaName":1506,"dataGaLocation":1605},{"text":1650,"config":1651},"Status",{"href":1652,"dataGaName":1653,"dataGaLocation":1605},"https://status.gitlab.com/","status",{"text":1655,"config":1656},"Terms of use",{"href":1657,"dataGaName":1658,"dataGaLocation":1605},"/terms/","terms of use",{"text":1660,"config":1661},"Privacy statement",{"href":1662,"dataGaName":1663,"dataGaLocation":1605},"/privacy/","privacy statement",{"text":1665,"config":1666},"Cookie preferences",{"dataGaName":1667,"dataGaLocation":1605,"id":1668,"isOneTrustButton":1146},"cookie preferences","ot-sdk-btn",{"title":1215,"links":1670,"subMenu":1679},[1671,1675],{"text":1672,"config":1673},"DevSecOps platform",{"href":1197,"dataGaName":1674,"dataGaLocation":1605},"devsecops platform",{"text":1676,"config":1677},"AI-Assisted Development",{"href":1204,"dataGaName":1678,"dataGaLocation":1605},"ai-assisted development",[1680],{"title":1681,"links":1682},"Topics",[1683,1687,1692,1697,1702,1707,1712,1717],{"text":1684,"config":1685},"CICD",{"href":38,"dataGaName":1686,"dataGaLocation":1605},"cicd",{"text":1688,"config":1689},"GitOps",{"href":1690,"dataGaName":1691,"dataGaLocation":1605},"/topics/gitops/","gitops",{"text":1693,"config":1694},"DevOps",{"href":1695,"dataGaName":1696,"dataGaLocation":1605},"/topics/devops/","devops",{"text":1698,"config":1699},"Version Control",{"href":1700,"dataGaName":1701,"dataGaLocation":1605},"/topics/version-control/","version control",{"text":1703,"config":1704},"DevSecOps",{"href":1705,"dataGaName":1706,"dataGaLocation":1605},"/topics/devsecops/","devsecops",{"text":1708,"config":1709},"Cloud Native",{"href":1710,"dataGaName":1711,"dataGaLocation":1605},"/topics/cloud-native/","cloud native",{"text":1713,"config":1714},"AI for Coding",{"href":1715,"dataGaName":1716,"dataGaLocation":1605},"/topics/devops/ai-for-coding/","ai for coding",{"text":1718,"config":1719},"Agentic AI",{"href":1720,"dataGaName":1721,"dataGaLocation":1605},"/topics/agentic-ai/","agentic ai",{"title":1723,"links":1724},"Solutions",[1725,1727,1729,1734,1738,1741,1745,1748,1750,1753,1756,1761],{"text":1256,"config":1726},{"href":1251,"dataGaName":1256,"dataGaLocation":1605},{"text":1245,"config":1728},{"href":1231,"dataGaName":1232,"dataGaLocation":1605},{"text":1730,"config":1731},"Agile development",{"href":1732,"dataGaName":1733,"dataGaLocation":1605},"/solutions/agile-delivery/","agile delivery",{"text":1735,"config":1736},"SCM",{"href":686,"dataGaName":1737,"dataGaLocation":1605},"source code management",{"text":1684,"config":1739},{"href":65,"dataGaName":1740,"dataGaLocation":1605},"continuous integration & delivery",{"text":1742,"config":1743},"Value stream management",{"href":1284,"dataGaName":1744,"dataGaLocation":1605},"value stream management",{"text":1688,"config":1746},{"href":1747,"dataGaName":1691,"dataGaLocation":1605},"/solutions/gitops/",{"text":1294,"config":1749},{"href":1297,"dataGaName":1298,"dataGaLocation":1605},{"text":1751,"config":1752},"Small business",{"href":1303,"dataGaName":1304,"dataGaLocation":1605},{"text":1754,"config":1755},"Public sector",{"href":1309,"dataGaName":1310,"dataGaLocation":1605},{"text":1757,"config":1758},"Education",{"href":1759,"dataGaName":1760,"dataGaLocation":1605},"/solutions/education/","education",{"text":1762,"config":1763},"Financial services",{"href":1764,"dataGaName":1765,"dataGaLocation":1605},"/solutions/finance/","financial services",{"title":1317,"links":1767},[1768,1770,1772,1774,1777,1779,1782,1784,1786,1788,1790,1792,1794,1796],{"text":1330,"config":1769},{"href":1332,"dataGaName":1333,"dataGaLocation":1605},{"text":1335,"config":1771},{"href":1337,"dataGaName":1338,"dataGaLocation":1605},{"text":1340,"config":1773},{"href":1342,"dataGaName":1343,"dataGaLocation":1605},{"text":1345,"config":1775},{"href":1347,"dataGaName":1776,"dataGaLocation":1605},"docs",{"text":1367,"config":1778},{"href":1369,"dataGaName":1370,"dataGaLocation":1605},{"text":1780,"config":1781},"What's new",{"href":1427,"dataGaName":1428,"dataGaLocation":1605},{"text":1362,"config":1783},{"href":1364,"dataGaName":1365,"dataGaLocation":1605},{"text":1381,"config":1785},{"href":1383,"dataGaName":1384,"dataGaLocation":1605},{"text":1389,"config":1787},{"href":1391,"dataGaName":1392,"dataGaLocation":1605},{"text":1394,"config":1789},{"href":1396,"dataGaName":1397,"dataGaLocation":1605},{"text":1399,"config":1791},{"href":1401,"dataGaName":1402,"dataGaLocation":1605},{"text":1404,"config":1793},{"href":1406,"dataGaName":1407,"dataGaLocation":1605},{"text":1409,"config":1795},{"href":1411,"dataGaName":1412,"dataGaLocation":1605},{"text":1414,"config":1797},{"href":1416,"dataGaName":1417,"dataGaLocation":1605},{"title":1430,"links":1799},[1800,1802,1804,1806,1808,1810,1814,1819,1821,1823,1825],{"text":1438,"config":1801},{"href":1440,"dataGaName":1432,"dataGaLocation":1605},{"text":1443,"config":1803},{"href":1445,"dataGaName":1446,"dataGaLocation":1605},{"text":1451,"config":1805},{"href":1453,"dataGaName":1454,"dataGaLocation":1605},{"text":1456,"config":1807},{"href":1458,"dataGaName":1459,"dataGaLocation":1605},{"text":1461,"config":1809},{"href":1463,"dataGaName":1464,"dataGaLocation":1605},{"text":1811,"config":1812},"Sustainability",{"href":1813,"dataGaName":1811,"dataGaLocation":1605},"/sustainability/",{"text":1815,"config":1816},"Diversity, inclusion and belonging (DIB)",{"href":1817,"dataGaName":1818,"dataGaLocation":1605},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":1466,"config":1820},{"href":1468,"dataGaName":1469,"dataGaLocation":1605},{"text":1476,"config":1822},{"href":1478,"dataGaName":1479,"dataGaLocation":1605},{"text":1481,"config":1824},{"href":1483,"dataGaName":1484,"dataGaLocation":1605},{"text":1826,"config":1827},"Modern Slavery Transparency Statement",{"href":1828,"dataGaName":1829,"dataGaLocation":1605},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":1831},[1832,1835,1838],{"text":1833,"config":1834},"Terms",{"href":1657,"dataGaName":1658,"dataGaLocation":1605},{"text":1836,"config":1837},"Cookies",{"dataGaName":1667,"dataGaLocation":1605,"id":1668,"isOneTrustButton":1146},{"text":1839,"config":1840},"Privacy",{"href":1662,"dataGaName":1663,"dataGaLocation":1605},[1842],{"id":1843,"title":7,"body":1142,"config":1844,"content":1846,"description":1142,"extension":1849,"meta":1850,"navigation":1146,"path":1851,"seo":1852,"stem":1853,"__hash__":1854},"blogAuthors/en-us/blog/authors/forrest-brazeal.yml",{"template":1845},"BlogAuthor",{"name":7,"config":1847},{"headshot":122,"ctfId":1848},"fbrazeal","yml",{},"/en-us/blog/authors/forrest-brazeal",{},"en-us/blog/authors/forrest-brazeal","-LJoNl2kFQ2-t5P9UDj-5kdXlZaHlvc9b_rG5JBTI2w",[1856,1865,1873],{"title":1857,"description":1858,"heroImage":1859,"category":1138,"date":1860,"authors":1861,"slug":1864,"externalUrl":1142},"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",[1862,1863],"Mathias Ewald","Martin Paloncy, Edgeless Systems","confidential-ai-for-gitlab-self-hosted",{"title":1866,"description":1867,"heroImage":1868,"category":1138,"date":1869,"authors":1870,"slug":1872,"externalUrl":1142},"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",[1871],"Lysanne Pinto","green-devops-carbon-measurement-cicd-pipeline",{"title":1874,"description":1875,"heroImage":1876,"category":1138,"date":1877,"authors":1878,"slug":1880,"externalUrl":1142},"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",[1879],"Paul Meresanu","how-to-build-ci-cd-observability-at-scale",{"promotions":1882},[1883,1897,1909,1921],{"id":1884,"categories":1885,"header":1887,"text":1888,"button":1889,"image":1894},"ai-modernization",[1886],"ai","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":1890,"config":1891},"Get your AI maturity score",{"href":1892,"dataGaName":1893,"dataGaLocation":1370},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":1895},{"src":1896},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":1898,"categories":1899,"header":1901,"text":1888,"button":1902,"image":1906},"devops-modernization",[1900,1706],"product","Are you just managing tools or shipping innovation?",{"text":1903,"config":1904},"Get your DevOps maturity score",{"href":1905,"dataGaName":1893,"dataGaLocation":1370},"/assessments/devops-modernization-assessment/",{"config":1907},{"src":1908},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":1910,"categories":1911,"header":1913,"text":1888,"button":1914,"image":1918},"security-modernization",[1912],"security","Are you trading speed for security?",{"text":1915,"config":1916},"Get your security maturity score",{"href":1917,"dataGaName":1893,"dataGaLocation":1370},"/assessments/security-modernization-assessment/",{"config":1919},{"src":1920},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"id":1922,"paths":1923,"header":1926,"text":1927,"button":1928,"image":1933},"github-azure-migration",[1924,1925],"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":1929,"config":1930},"See how GitLab compares to GitHub",{"href":1931,"dataGaName":1932,"dataGaLocation":1370},"/compare/gitlab-vs-github/github-azure-migration/","github azure migration",{"config":1934},{"src":1908},{"header":1936,"blurb":1937,"button":1938,"secondaryButton":1943},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":1939,"config":1940},"Get your free trial",{"href":1941,"dataGaName":1173,"dataGaLocation":1942},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":1643,"config":1944},{"href":1495,"dataGaName":1178,"dataGaLocation":1942},1786803753409]