[{"data":1,"prerenderedAt":720},["ShallowReactive",2],{"/en-us/blog/managing-gitlab-resources-with-pulumi/":3,"navigation-en-us":37,"banner-en-us":463,"footer-en-us":480,"Josh Kodroff, Pulumi":690,"next-steps-en-us":705},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":27,"_id":30,"_type":31,"title":32,"_source":33,"_file":34,"_stem":35,"_extension":36},"/en-us/blog/managing-gitlab-resources-with-pulumi","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Managing GitLab resources with Pulumi","Learn how Pulumi's infrastructure-as-code tool helps streamline the automation of GitLab CI/CD workflows.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683430/Blog/Hero%20Images/AdobeStock_293854129__1_.jpg","https://about.gitlab.com/blog/managing-gitlab-resources-with-pulumi","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Managing GitLab resources with Pulumi\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Josh Kodroff, Pulumi\"}],\n        \"datePublished\": \"2024-01-10\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Josh Kodroff, Pulumi","2024-01-10","In the ever-evolving landscape of DevOps, platform engineers are\nincreasingly seeking efficient and flexible tools to manage their GitLab\nresources, particularly for orchestrating continuous integration/continuous\ndelivery (CI/CD) pipelines.\n[Pulumi](https://pulumi.com?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\noffers a unique approach to infrastructure as code (IaC) by allowing\nengineers to use familiar programming languages such as TypeScript, Python,\nGo, and others. This approach streamlines the automation of GitLab CI/CD\nworkflows. Pulumi's declarative syntax, combined with its ability to treat\ninfrastructure as software, facilitates version control, collaboration, and\nreproducibility, aligning seamlessly with the GitLab philosophy.\n\n\nLet's explore the power of using Pulumi and GitLab.\n\n\n## What is Pulumi?\n\n\nPulumi is an IaC tool that allows you to manage resources in more than 150\nsupported cloud or SaaS products (including AWS and GitLab, which we will be\ndemonstrating in this post). You can express your infrastructure with Pulumi\nusing popular general-purpose programming languages like TypeScript, Python,\nand Go.\n\n\nPulumi is declarative (just like other popular IaC tools you may be familiar\nwith), which means that you only need to describe the desired end state of\nyour resources and Pulumi will figure out the order of create, read, update,\nand delete (CRUD) operations to get from your current state to your desired\nstate.\n\n\nIt might seem strange at first to use a general-purpose programming language\nto express your infrastructure's desired state if you're used to tools like\nCloudFormation or Terraform, but there are considerable advantages to\nPulumi's approach, including the following:\n\n- **Familiar tooling.** You don't need any special tooling to use Pulumi.\nCode completion will work as expected in your favorite editor or IDE without\nany additional plugins. You can share Pulumi code using familiar packaging\ntools like npm, PyPI, etc.\n\n- **Familiar syntax.** Unlike with DSL-based IaC tools, you don't need to\nlearn special ways of indexing an array element, or creating loops or\nconditionals - you can just use the normal syntax of a language you already\nknow.\n\n\nThe Pulumi product has an open source component, which includes the Pulumi\ncommand line and its ecosystem of providers, which provide the integration\nbetween Pulumi and the cloud and SaaS providers it supports. Pulumi also\noffers a free (for individual use) and paid (for teams and organizations)\nSaaS service called Pulumi Cloud, which provides state file and secrets\nmanagement, among many other useful features. It’s a widely-supported\nopen-source IaC tool.\n\n\n## Initializing the project\n\n\nTo complete this example you'll need:\n\n\n1. [A Pulumi Cloud\naccount](https://app.pulumi.com?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources).\nPulumi Cloud is free for individual use forever and we'll never ask for your\ncredit card. Pulumi Cloud will manage your Pulumi state file and handle any\nsecrets encryption/decryption. Because it's free for individual use (no\ncredit card required), we strongly recommend that you use Pulumi Cloud as\nyour backend when learning how to use Pulumi.\n\n2. A GitLab account, group, and a GitLab token set to the `GITLAB_TOKEN`\nenvironment variable.\n\n3. An AWS account and credentials with permissions to deploy identity and\naccess management (IAM) resources. For details on how to configure AWS\ncredentials on your system for use with Pulumi, see [AWS Classic:\nInstallation and\nConfiguration](https://www.pulumi.com/registry/packages/aws/installation-configuration/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources).\n\n\nThis example will use two providers from the [Pulumi\nRegistry](https://www.pulumi.com/registry/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources):\n\n\n1. The [GitLab\nProvider](https://www.pulumi.com/registry/packages/gitlab/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nwill be used to manage resources like Projects, ProjectFiles (to initialize\nour project repository), ProjectHooks (for the integration with Pulumi\nCloud), and ProjectVariables (to hold configuration for our CI/CD\npipelines).\n\n2. The [AWS Classic\nProvider](https://www.pulumi.com/registry/packages/aws/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nwill be used to manage AWS resources to create OpenID Connect (OIDC)\nconnectivity between AWS and GitLab.\n\n\nYou can initialize your Pulumi project by changing into a new, empty\ndirectory, running the following command, and accepting all the default\nvalues for any subsequent prompts:\n\n\n```bash\n\npulumi new typescript\n\n```\n\n\nThis will bootstrap an empty Pulumi program. Now you can import the provider\nSDKs for the providers you'll need:\n\n\n```bash\n\nnpm i @pulumi/aws @pulumi/gitlab\n\n```\n\n\nYour `index.ts` file is the entry point into your Pulumi program (just as\nyou would expect in any other Node.js program) and will be the file to which\nyou will add your resources. Add the following imports to the top of\n`index.ts`:\n\n\n```typescript\n\nimport * as gitlab from \"@pulumi/gitlab\";\n\nimport * as aws from \"@pulumi/aws\";\n\n```\n\n\nNow you are ready to add some resources!\n\n\n## Adding your first resources\n\n\nFirst, let's define a variable that will hold the audience claim in our OIDC\nJWT token. Add the following code to `index.ts`:\n\n\n```typescript\n\nconst audience = \"gitlab.com\";\n\n```\n\n\nThe above code assume you're using the GitLab SaaS (\u003Chttps://gitlab.com>) If\nyou are using a private GitLab install, your value should be the domain of\nyour GitLab install, e.g. `gitlab.example.com`.\n\n\nThen, you'll use a [Pulumi\nfunction](https://www.pulumi.com/docs/concepts/resources/functions/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nto grab an existing GitLab group by name and create a new public GitLab\nproject in your GitLab group:\n\n\n```typescript\n\nconst group = gitlab.getGroup({\n  fullPath: \"my-gitlab-group\", // Replace the value with the name of your GL group\n});\n\n\nconst project = new gitlab.Project(\"pulumi-gitlab-demo\", {\n  visibilityLevel: \"public\",\n  defaultBranch: \"main\",\n  namespaceId: group.then(g => parseInt(g.id)),\n  archiveOnDestroy: false // Be sure to set this to `true` for any non-demo repos you manage with Pulumi!\n});\n\n```\n\n\n## Creating OIDC resources\n\n\nTo allow GitLab CI/CD to request and be granted temporary AWS credentials,\nyou'll need to create an OIDC provider in AWS that contains the thumbprint\nof GitLab's certificate, and then create an AWS role that GitLab is allowed\nto assume.\n\n\nYou'll scope the assume role policy so that the role can be only be assumed\nby the GitLab project you declared earlier. The role that GitLab CI/CD\nassumed will have full administrator access so that Pulumi can create and\nmanage any resource within AWS. (Note that it is possible to grant less than\n`FullAdministrator` access to Pulumi, but `FullAdministrator` is often\npractically required, e.g. where IAM resources, like roles, need to be\ncreated. Role creation requires `FullAdministrator`. This consideration also\napplies to IaC tools like Terraform.)\n\n\nAdd the following code to `index.ts`:\n\n\n```typescript\n\nconst GITLAB_OIDC_PROVIDER_THUMBPRINT =\n\"b3dd7606d2b5a8b4a13771dbecc9ee1cecafa38a\";\n\n\nconst gitlabOidcProvider = new\naws.iam.OpenIdConnectProvider(\"gitlab-oidc-provider\", {\n  clientIdLists: [`https://${audience}`],\n  url: `https://${audience}`,\n  thumbprintLists: [GITLAB_OIDC_PROVIDER_THUMBPRINT],\n}, {\n  deleteBeforeReplace: true, // URLs are unique identifiers and cannot be auto-named, so we have to delete before replace.\n});\n\n\nconst gitlabAdminRole = new aws.iam.Role(\"gitlabAdminRole\", {\n  assumeRolePolicy: {\n    Version: \"2012-10-17\",\n    Statement: [\n      {\n        Effect: \"Allow\",\n        Principal: {\n          Federated: gitlabOidcProvider.arn,\n        },\n        Action: \"sts:AssumeRoleWithWebIdentity\",\n        Condition: {\n          StringLike: {\n            // Note: Square brackets around the key are what allow us to use a\n            // templated string. See:\n            // https://stackoverflow.com/questions/59791960/how-to-use-template-literal-as-key-inside-object-literal\n            [`${audience}:sub`]: pulumi.interpolate`project_path:${project.pathWithNamespace}:ref_type:branch:ref:*`\n          },\n        },\n      },\n    ],\n  },\n});\n\n\nnew aws.iam.RolePolicyAttachment(\"gitlabAdminRolePolicy\", {\n  policyArn: \"arn:aws:iam::aws:policy/AdministratorAccess\",\n  role: gitlabAdminRole.name,\n});\n\n```\n\n\nA few things to be aware of regarding the thumbprint:\n\n\n1. If you are self-hosting GitLab, you'll need to obtain the thumbprint from\nyour private GitLab installation.\n\n2. If you're using GitLab SaaS, it's possible GitLab's OIDC certificate may\nhave been rotated by the time you are reading this.\n\n\nIn either case, you can obtain the correct/latest thumbprint value by\nfollowing AWS' instructions contained in [Obtaining the thumbprint for an\nOpenID Connect Identity\nProvider](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers_create_oidc_verify-thumbprint.html)\nin the AWS docs.\n\n\nYou'll also need to add the role's ARN as a project variable so that the\nCI/CD process can make a request to assume the role:\n\n\n```typescript\n\nnew gitlab.ProjectVariable(\"role-arn\", {\n  project: project.id,\n  key: \"ROLE_ARN\",\n  value: gitlabAdminRole.arn,\n});\n\n```\n\n\n## Project hook (optional)\n\n\nPulumi features an integration with GitLab via a webhook that will post the\noutput of the `pulumi preview` directly to a merge request as a comment. For\nthe webhook to work, you must have a Pulumi organization set up with GitLab\nas its SSO source. If you don't have a Pulumi organization and would like to\ntry the integration, you can [sign up for a free\ntrial](https://app.pulumi.com/signup?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\norganization. The trial lasts 14 days, will give you access to all of\nPulumi's paid features, and does not require a credit card. For full details\non the integration, see [Pulumi CI/CD & GitLab\nintegration](https://www.pulumi.com/docs/using-pulumi/continuous-delivery/gitlab-app/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources).\n\n\nTo set up the webhook, add the following to your `index.ts` file:\n\n\n```typescript\n\nnew gitlab.ProjectHook(\"project-hook\", {\n  project: project.id,\n  url: \"https://api.pulumi.com/workflow/gitlab\",\n  mergeRequestsEvents: true,\n  enableSslVerification: true,\n  token: process.env[\"PULUMI_ACCESS_TOKEN\"]!,\n  pushEvents: false,\n});\n\n```\n\n\nNote that the above resource assumes that your Pulumi access token is stored\nas an environment variable. You may want to instead store the token in your\nstack configuration file. To do this, run the following command:\n\n\n```bash\n\npulumi config set --secret pulumiAccessToken ${PULUMI_ACCESS_TOKEN}\n\n```\n\n\nThis will store the encrypted value in your Pulumi stack configuration file\n(`Pulumi.dev.yaml`). Because the value is encrypted, you can safely commit\nyour stack configuration file to git. You can access its value in your\nPulumi program like this:\n\n\n```typescript\n\nconst config = new pulumi.Config();\n\nconst pulumiAccessToken = config.requireSecret(\"pulumiAccessToken\");\n\n```\n\n\nFor more details on secrets handling in Pulumi, see\n[Secrets](https://www.pulumi.com/docs/concepts/secrets/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nin the Pulumi docs.\n\n\n## Creating a repository and adding repository files\n\n\nYou'll need to create a git repository (a GitLab project) and add some files\nto it that will control the CI/CD process. First, create some files that\nyou'll include in your GitLab repo:\n\n\n```bash\n\nmkdir -p repository-files/scripts\n\ntouch repository-files/.gitlab-ci.yml\nrepository-files/scripts/{aws-auth.sh,pulumi-preview.sh,pulumi-up.sh}\n\nchmod +x\nrepository-files/scripts/{aws-auth.sh,pulumi-preview.sh,pulumi-up.sh}\n\n```\n\n\nNext, you'll need a GitLab CI/CD YAML file to describe the pipeline: which\ncontainer image it should be run in and what the steps of the pipeline are.\nPlace the following code into `repository-files/.gitlab-ci.yml`:\n\n\n```yaml\n\ndefault:\n  image:\n    name: \"pulumi/pulumi:3.91.1\"\n    entrypoint: [\"\"]\n\nstages:\n  - infrastructure-update\n\npulumi-up:\n  stage: infrastructure-update\n  id_tokens:\n    GITLAB_OIDC_TOKEN:\n      aud: https://gitlab.com\n  before_script:\n    - chmod +x ./scripts/*.sh\n    - ./scripts/aws-auth.sh\n  script:\n    - ./scripts/pulumi-up.sh\n  only:\n    - main # i.e., the name of the default branch\n\npulumi-preview:\n  stage: infrastructure-update\n  id_tokens:\n    GITLAB_OIDC_TOKEN:\n      aud: https://gitlab.com\n  before_script:\n    - chmod +x ./scripts/*.sh\n    - ./scripts/aws-auth.sh\n  script:\n    - ./scripts/pulumi-preview.sh\n  rules:\n    - if: $CI_PIPELINE_SOURCE == 'merge_request_event'\n```\n\n\nThe CI/CD process is fairly simple but illustrates the basic functionality\nneeded for a production-ready pipeline (or these steps may be all your\norganization needs):\n\n\n1. Run the `pulumi preview` command when a merge request is opened or\nupdated. This will help the reviewer gain important context. Because IaC is\nnecessarily stateful (the state file is what enables Pulumi to be a\ndeclarative tool), when reviewing changes reviewers _must have both the code\nchanges and the infrastructure changes to fully understand the impact of\nchanges to the codebase_. This process constitutes continuous integration.\n\n2. Run the `pulumi up` command when code is merged to the default branch\n(called `main` by default). This process constitutes continuous delivery.\n\n\nNote that this example uses the\n[`pulumi/pulumi`](https://hub.docker.com/r/pulumi/pulumi) \"kitchen sink\"\nimage that contains all the runtimes for all the languages Pulumi supports,\nalong with some ancillary tools like the AWS CLI (which you'll need in order\nto use OIDC authentication). While the `pulumi/pulumi` image is convenient,\nit's also quite large (1.41 GB at the time of writing), which makes it\nrelatively slow to initialize. If you're creating production pipelines using\nPulumi, you may want to consider creating your own custom (slimmer) image\nthat has exactly the tools you need installed, perhaps starting with one of\nPulumi's language-specific images, e.g.\n[`pulumi/pulumi-nodejs`](https://hub.docker.com/r/pulumi/pulumi-nodejs).\n\n\nThen you'll need to write the script that authenticates GitLab with AWS via\nOIDC. Place the following code in `repository-files/scripts/aws-auth.sh`:\n\n\n```bash\n\n#!/bin/bash\n\n\nmkdir -p ~/.aws\n\necho \"${GITLAB_OIDC_TOKEN}\" > /tmp/web_identity_token\n\necho -e \"[profile\noidc]\\nrole_arn=${ROLE_ARN}\\nweb_identity_token_file=/tmp/web_identity_token\"\n> ~/.aws/config\n\n\necho \"length of GITLAB_OIDC_TOKEN=${#GITLAB_OIDC_TOKEN}\"\n\necho \"ROLE_ARN=${ROLE_ARN}\"\n\n\nexport AWS_PROFILE=\"oidc\"\n\naws sts get-caller-identity\n\n```\n\n\nFor continuous integration, you'll need a script that will execute the\n`pulumi preview` command when a merge request is opened. Place the following\ncode in `repository-files/scripts/pulumi-preview.sh`:\n\n\n```bash\n\n#!/bin/bash\n\nset -e -x\n\n\nexport PATH=$PATH:$HOME/.pulumi/bin\n\n\nyarn install\n\npulumi login\n\npulumi org set-default $PULUMI_ORG\n\npulumi stack select dev\n\nexport AWS_PROFILE=\"oidc\"\n\npulumi preview\n\n```\n\n\nFor continuous delivery, you'll need a similar script that will execute the\n`pulumi up` command when the Merge Request is merged to the default branch.\nPlace the following code in `repository-files/scripts/pulumi-up.sh`:\n\n\n```bash\n\n#!/bin/bash\n\nset -e -x\n\n\n# Add the pulumi CLI to the PATH\n\nexport PATH=$PATH:$HOME/.pulumi/bin\n\n\nyarn install\n\npulumi login\n\npulumi org set-default $PULUMI_ORG\n\npulumi stack select dev\n\nexport AWS_PROFILE=\"oidc\"\n\npulumi up -y\n\n```\n\n\nFinally, you'll need to add these files to your GitLab Project. Add the\nfollowing code block to your `index.ts` file:\n\n\n```typescript\n\n[\n  \"scripts/aws-auth.sh\",\n  \"scripts/pulumi-preview.sh\",\n  \"scripts/pulumi-up.sh\",\n  \".gitlab-ci.yml\",\n].forEach(file => {\n  const content = fs.readFileSync(`repository-files/${file}`, \"utf-8\");\n\n  new gitlab.RepositoryFile(file, {\n    project: project.id,\n    filePath: file,\n    branch: \"main\",\n    content: content,\n    commitMessage: `Add ${file},`,\n    encoding: \"text\",\n  });\n});\n\n```\n\n\nNote that we're able to take advantage of general-purpose programming\nlanguage features: We are able to create an array and use `forEach()` to\niterate through its members, and we are able to use the `fs.readFileSync()`\nmethod from the Node.js runtime to read the contents of our file. This is\npowerful stuff!\n\n\n## Project variables and stack outputs\n\n\nYou'll need a few more resources to complete the code. Your CI/CD process\nwill need a Pulumi access token in order to authenticate against the Pulumi\nCloud backend which holds your Pulumi state file and handles encryption and\ndecryption of secrets. You will also need to supply name of your Pulumi\norganization. (If you are using Pulumi Cloud as an individual, this is your\nPulumi username.) Add the following to `index.ts`:\n\n\n```typescript\n\nnew gitlab.ProjectVariable(\"pulumi-access-token\", {\n  project: project.id,\n  key: \"PULUMI_ACCESS_TOKEN\",\n  value: process.env[\"PULUMI_ACCESS_TOKEN\"]!,\n  masked: true,\n});\n\n\nnew gitlab.ProjectVariable(\"pulumi-org\", {\n  project: project.id,\n  key: \"PULUMI_ORG\",\n  value: pulumi.getOrganization(),\n});\n\n```\n\n\nFinally, you'll need to add a stack output so that we can run the `git\nclone` command to test out our pipeline. Stack outputs allow you to access\nvalues within your Pulumi program from the command line or from other Pulumi\nprograms. For more information, see [Understanding Stack\nOutputs](https://www.pulumi.com/learn/building-with-pulumi/stack-outputs/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources).\nAdd the following to `index.ts`:\n\n\n```typescript\n\nexport const gitCloneCommand = pulumi.interpolate`git clone\n${project.sshUrlToRepo}`;\n\n```\n\n\n## Deploying your infrastructure and testing the pipeline\n\n\nTo deploy your resources, run the following command:\n\n\n```bash\n\npulumi up\n\n```\n\n\nPulumi will output a list of the resources it intends to create. Select\n`yes` to continue.\n\n\nOnce the command has completed, you can run the following command to get the\ngit clone command for your GitLab repo:\n\n\n```bash\n\npulumi stack output gitCloneCommand\n\n```\n\n\nIn a new, empty directory, run the `git clone` command from your Pulumi\nstack output, e.g.:\n\n\n```bash\n\ngit clone git@gitlab.com:jkodroff/pulumi-gitlab-demo-9de2a3b.git\n\n```\n\n\nChange into the directory and create a new branch:\n\n\n```bash\n\ngit checkout -b my-first-branch\n\n```\n\n\nNow you are ready to create some sample infrastructure in our repository.\nYou can use the `aws-typescript` to quickly generate a simple Pulumi program\nwith AWS resources:\n\n\n```bash\n\npulumi new aws-typescript -y --force\n\n```\n\n\nThe template includes a very simple Pulumi program that you can use to prove\nout the pipeline:\n\n\n```bash\n\n$ cat index.ts\n\nimport * as pulumi from \"@pulumi/pulumi\";\n\nimport * as aws from \"@pulumi/aws\";\n\nimport * as awsx from \"@pulumi/awsx\";\n\n\n// Create an AWS resource (S3 Bucket)\n\nconst bucket = new aws.s3.Bucket(\"my-bucket\");\n\n\n// Export the name of the bucket\n\nexport const bucketName = bucket.id;\n\n```\n\n\nCommit your changes and push your branch:\n\n\n```bash\n\ngit add -A\n\ngit commit -m \"My first commit.\"\n\ngit push\n\n```\n\n\nIn the GitLab UI, create a merge request for your branch:\n\n\n![Screenshot demonstrating opening a GitLab Merge\nRequest](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/create-merge-request.jpg)\n\n\nYour merge request pipeline should start running:\n\n\n![Screenshot demonstrating opening a GitLab Merge\nRequest](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/merge-request-running.jpg)\n\n\nOnce the pipeline completes, you should see the output of the `pulumi\npreview` command in the pipeline's logs:\n\n\n![Screenshot of a GitLab pipeline log showing the output of the \"pulumi\npreview\"\ncommand](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/pulumi-preview.jpg)\n\n\nIf you installed the optional webhook, you should see the results of `pulumi\npreview` posted back to the merge request as a comment:\n\n\n![Screenshot of the GitLab Merge Request screen showing the output of the\n\"pulumi preview\" command as a\ncomment](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/merge-request-comment.jpg)\n\n\nOnce the pipeline has completed running, your merge request is ready to\nmerge:\n\n\n![Screenshot of the GitLab Merge Request screen showing a successfully\ncompleted\npipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/merge.jpg)\n\n\nMerging the merge request will trigger the main branch pipeline. (Note that\nin this screen you will see a failed initial run of CI/CD on the main branch\ntoward the bottom of the screen. This is normal and is caused by the initial\nupload of `.gitlab-ci/yml` to the main branch without a Pulumi program being\npresent.)\n\n\n![Screenshot of the GitLab pipelines screen showing a running pipeline along\nwith a passed\npipelines](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/piplines.jpg)\n\n\nIf you click into the main branch pipeline's execution, you can see your\nbucket has been created:\n\n\n![Screenshot of a GitLab pipeline log showing the output of the \"pulumi up\"\ncommand](https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683438/Blog/Content%20Images/pulumi-up.jpg)\n\nTo delete the bucket, run the following command in your local clone of the\nrepository:\n\n\n```bash\n\npulumi destroy\n\n```\n\n\nAlternatively, you could create a merge request that removes the bucket from\nyour Pulumi program and run the pipelines again. Because Pulumi is\ndeclarative, removing the bucket from your program will delete it from AWS.\n\n\nFinally, run the `pulumi destroy` command again in the Pulumi program with\nyour OIDC and GitLab resources to finish cleaning up.\n\n\n## Next steps\n\n\nUsing IaC to define pipelines and other GitLab resources can greatly improve\nyour platform team's ability to reliably and quickly manage the resources to\nkeep application teams delivering. With Pulumi, you also get the power and\nexpressiveness of using popular programming languages to express those\nresources!\n\n\nIf you liked what you read here, here are some ways you can enhance your\nCI/CD pipelines:\n\n\n- Add [Pulumi Policy\nPacks](https://www.pulumi.com/docs/using-pulumi/crossguard/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nto your pipeline: Pulumi policy packs allow you to validate that your\nresources are in compliance with your organization's security and compliance\npolicies. Pulumi's open source [Compliance Ready\nPolicies](https://www.pulumi.com/docs/using-pulumi/crossguard/compliance-ready-policies/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\nare a great place to start on your journey. Compliance Ready Policies\ncontain policy rules for the major cloud providers for popular compliance\nframeworks like PCI-DSS and ISO27001, and policy packs are easy to integrate\ninto your pipelines.\n\n- Check out [Pulumi ESC (Environments, Secrets, and\nConfiguration)](https://www.pulumi.com/product/esc/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources):\nPulumi ESC makes it easy to share static secrets like GitLab tokens and can\neven [generate dynamic secrets like AWS OIDC\ncredentials](https://www.pulumi.com/blog/esc-env-run-aws/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources).\nESC becomes especially useful when using Pulumi at scale because it reduces\nthe duplication of configuration and secrets that are used by multiple\nPulumi programs. You don't even have to use Pulumi IaC to benefit from\nPulumi ESC - [Pulumi ESC's command\nline](https://www.pulumi.com/docs/esc-cli/commands/?utm_source=GitLab&utm_medium=Referral&utm_campaign=Managing-GitLab-Resources)\ncan be used with any CLI tool like the AWS CLI.\n","devsecops",[23,24,25,26],"CI/CD","DevSecOps","partners","integrations",{"slug":28,"featured":6,"template":29},"managing-gitlab-resources-with-pulumi","BlogPost","content:en-us:blog:managing-gitlab-resources-with-pulumi.yml","yaml","Managing Gitlab Resources With Pulumi","content","en-us/blog/managing-gitlab-resources-with-pulumi.yml","en-us/blog/managing-gitlab-resources-with-pulumi","yml",{"_path":38,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":40,"_id":459,"_type":31,"title":460,"_source":33,"_file":461,"_stem":462,"_extension":36},"/shared/en-us/main-navigation","en-us",{"logo":41,"freeTrial":46,"sales":51,"login":56,"items":61,"search":390,"minimal":421,"duo":440,"pricingDeployment":449},{"config":42},{"href":43,"dataGaName":44,"dataGaLocation":45},"/","gitlab logo","header",{"text":47,"config":48},"Get free trial",{"href":49,"dataGaName":50,"dataGaLocation":45},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":52,"config":53},"Talk to sales",{"href":54,"dataGaName":55,"dataGaLocation":45},"/sales/","sales",{"text":57,"config":58},"Sign in",{"href":59,"dataGaName":60,"dataGaLocation":45},"https://gitlab.com/users/sign_in/","sign in",[62,106,203,208,311,371],{"text":63,"config":64,"cards":66,"footer":89},"Platform",{"dataNavLevelOne":65},"platform",[67,73,81],{"title":63,"description":68,"link":69},"The most comprehensive AI-powered DevSecOps Platform",{"text":70,"config":71},"Explore our Platform",{"href":72,"dataGaName":65,"dataGaLocation":45},"/platform/",{"title":74,"description":75,"link":76},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":77,"config":78},"Meet GitLab Duo",{"href":79,"dataGaName":80,"dataGaLocation":45},"/gitlab-duo/","gitlab duo ai",{"title":82,"description":83,"link":84},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":85,"config":86},"Learn more",{"href":87,"dataGaName":88,"dataGaLocation":45},"/why-gitlab/","why gitlab",{"title":90,"items":91},"Get started with",[92,97,102],{"text":93,"config":94},"Platform Engineering",{"href":95,"dataGaName":96,"dataGaLocation":45},"/solutions/platform-engineering/","platform engineering",{"text":98,"config":99},"Developer Experience",{"href":100,"dataGaName":101,"dataGaLocation":45},"/developer-experience/","Developer experience",{"text":103,"config":104},"MLOps",{"href":105,"dataGaName":103,"dataGaLocation":45},"/topics/devops/the-role-of-ai-in-devops/",{"text":107,"left":108,"config":109,"link":111,"lists":115,"footer":185},"Product",true,{"dataNavLevelOne":110},"solutions",{"text":112,"config":113},"View all Solutions",{"href":114,"dataGaName":110,"dataGaLocation":45},"/solutions/",[116,140,164],{"title":117,"description":118,"link":119,"items":124},"Automation","CI/CD and automation to accelerate deployment",{"config":120},{"icon":121,"href":122,"dataGaName":123,"dataGaLocation":45},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[125,128,132,136],{"text":23,"config":126},{"href":127,"dataGaLocation":45,"dataGaName":23},"/solutions/continuous-integration/",{"text":129,"config":130},"AI-Assisted Development",{"href":79,"dataGaLocation":45,"dataGaName":131},"AI assisted development",{"text":133,"config":134},"Source Code Management",{"href":135,"dataGaLocation":45,"dataGaName":133},"/solutions/source-code-management/",{"text":137,"config":138},"Automated Software Delivery",{"href":122,"dataGaLocation":45,"dataGaName":139},"Automated software delivery",{"title":141,"description":142,"link":143,"items":148},"Security","Deliver code faster without compromising security",{"config":144},{"href":145,"dataGaName":146,"dataGaLocation":45,"icon":147},"/solutions/security-compliance/","security and compliance","ShieldCheckLight",[149,154,159],{"text":150,"config":151},"Application Security Testing",{"href":152,"dataGaName":153,"dataGaLocation":45},"/solutions/application-security-testing/","Application security testing",{"text":155,"config":156},"Software Supply Chain Security",{"href":157,"dataGaLocation":45,"dataGaName":158},"/solutions/supply-chain/","Software supply chain security",{"text":160,"config":161},"Software Compliance",{"href":162,"dataGaName":163,"dataGaLocation":45},"/solutions/software-compliance/","software compliance",{"title":165,"link":166,"items":171},"Measurement",{"config":167},{"icon":168,"href":169,"dataGaName":170,"dataGaLocation":45},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[172,176,180],{"text":173,"config":174},"Visibility & Measurement",{"href":169,"dataGaLocation":45,"dataGaName":175},"Visibility and Measurement",{"text":177,"config":178},"Value Stream Management",{"href":179,"dataGaLocation":45,"dataGaName":177},"/solutions/value-stream-management/",{"text":181,"config":182},"Analytics & Insights",{"href":183,"dataGaLocation":45,"dataGaName":184},"/solutions/analytics-and-insights/","Analytics and insights",{"title":186,"items":187},"GitLab for",[188,193,198],{"text":189,"config":190},"Enterprise",{"href":191,"dataGaLocation":45,"dataGaName":192},"/enterprise/","enterprise",{"text":194,"config":195},"Small Business",{"href":196,"dataGaLocation":45,"dataGaName":197},"/small-business/","small business",{"text":199,"config":200},"Public Sector",{"href":201,"dataGaLocation":45,"dataGaName":202},"/solutions/public-sector/","public sector",{"text":204,"config":205},"Pricing",{"href":206,"dataGaName":207,"dataGaLocation":45,"dataNavLevelOne":207},"/pricing/","pricing",{"text":209,"config":210,"link":212,"lists":216,"feature":298},"Resources",{"dataNavLevelOne":211},"resources",{"text":213,"config":214},"View all resources",{"href":215,"dataGaName":211,"dataGaLocation":45},"/resources/",[217,249,271],{"title":218,"items":219},"Getting started",[220,225,230,235,240,245],{"text":221,"config":222},"Install",{"href":223,"dataGaName":224,"dataGaLocation":45},"/install/","install",{"text":226,"config":227},"Quick start guides",{"href":228,"dataGaName":229,"dataGaLocation":45},"/get-started/","quick setup checklists",{"text":231,"config":232},"Learn",{"href":233,"dataGaLocation":45,"dataGaName":234},"https://university.gitlab.com/","learn",{"text":236,"config":237},"Product documentation",{"href":238,"dataGaName":239,"dataGaLocation":45},"https://docs.gitlab.com/","product documentation",{"text":241,"config":242},"Best practice videos",{"href":243,"dataGaName":244,"dataGaLocation":45},"/getting-started-videos/","best practice videos",{"text":246,"config":247},"Integrations",{"href":248,"dataGaName":26,"dataGaLocation":45},"/integrations/",{"title":250,"items":251},"Discover",[252,257,261,266],{"text":253,"config":254},"Customer success stories",{"href":255,"dataGaName":256,"dataGaLocation":45},"/customers/","customer success stories",{"text":258,"config":259},"Blog",{"href":260,"dataGaName":5,"dataGaLocation":45},"/blog/",{"text":262,"config":263},"Remote",{"href":264,"dataGaName":265,"dataGaLocation":45},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":267,"config":268},"TeamOps",{"href":269,"dataGaName":270,"dataGaLocation":45},"/teamops/","teamops",{"title":272,"items":273},"Connect",[274,279,284,289,294],{"text":275,"config":276},"GitLab Services",{"href":277,"dataGaName":278,"dataGaLocation":45},"/services/","services",{"text":280,"config":281},"Community",{"href":282,"dataGaName":283,"dataGaLocation":45},"/community/","community",{"text":285,"config":286},"Forum",{"href":287,"dataGaName":288,"dataGaLocation":45},"https://forum.gitlab.com/","forum",{"text":290,"config":291},"Events",{"href":292,"dataGaName":293,"dataGaLocation":45},"/events/","events",{"text":295,"config":296},"Partners",{"href":297,"dataGaName":25,"dataGaLocation":45},"/partners/",{"backgroundColor":299,"textColor":300,"text":301,"image":302,"link":306},"#2f2a6b","#fff","Insights for the future of software development",{"altText":303,"config":304},"the source promo card",{"src":305},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":307,"config":308},"Read the latest",{"href":309,"dataGaName":310,"dataGaLocation":45},"/the-source/","the source",{"text":312,"config":313,"lists":315},"Company",{"dataNavLevelOne":314},"company",[316],{"items":317},[318,323,329,331,336,341,346,351,356,361,366],{"text":319,"config":320},"About",{"href":321,"dataGaName":322,"dataGaLocation":45},"/company/","about",{"text":324,"config":325,"footerGa":328},"Jobs",{"href":326,"dataGaName":327,"dataGaLocation":45},"/jobs/","jobs",{"dataGaName":327},{"text":290,"config":330},{"href":292,"dataGaName":293,"dataGaLocation":45},{"text":332,"config":333},"Leadership",{"href":334,"dataGaName":335,"dataGaLocation":45},"/company/team/e-group/","leadership",{"text":337,"config":338},"Team",{"href":339,"dataGaName":340,"dataGaLocation":45},"/company/team/","team",{"text":342,"config":343},"Handbook",{"href":344,"dataGaName":345,"dataGaLocation":45},"https://handbook.gitlab.com/","handbook",{"text":347,"config":348},"Investor relations",{"href":349,"dataGaName":350,"dataGaLocation":45},"https://ir.gitlab.com/","investor relations",{"text":352,"config":353},"Trust Center",{"href":354,"dataGaName":355,"dataGaLocation":45},"/security/","trust center",{"text":357,"config":358},"AI Transparency Center",{"href":359,"dataGaName":360,"dataGaLocation":45},"/ai-transparency-center/","ai transparency center",{"text":362,"config":363},"Newsletter",{"href":364,"dataGaName":365,"dataGaLocation":45},"/company/contact/","newsletter",{"text":367,"config":368},"Press",{"href":369,"dataGaName":370,"dataGaLocation":45},"/press/","press",{"text":372,"config":373,"lists":374},"Contact us",{"dataNavLevelOne":314},[375],{"items":376},[377,380,385],{"text":52,"config":378},{"href":54,"dataGaName":379,"dataGaLocation":45},"talk to sales",{"text":381,"config":382},"Get help",{"href":383,"dataGaName":384,"dataGaLocation":45},"/support/","get help",{"text":386,"config":387},"Customer portal",{"href":388,"dataGaName":389,"dataGaLocation":45},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":391,"login":392,"suggestions":399},"Close",{"text":393,"link":394},"To search repositories and projects, login to",{"text":395,"config":396},"gitlab.com",{"href":59,"dataGaName":397,"dataGaLocation":398},"search login","search",{"text":400,"default":401},"Suggestions",[402,404,408,410,414,418],{"text":74,"config":403},{"href":79,"dataGaName":74,"dataGaLocation":398},{"text":405,"config":406},"Code Suggestions (AI)",{"href":407,"dataGaName":405,"dataGaLocation":398},"/solutions/code-suggestions/",{"text":23,"config":409},{"href":127,"dataGaName":23,"dataGaLocation":398},{"text":411,"config":412},"GitLab on AWS",{"href":413,"dataGaName":411,"dataGaLocation":398},"/partners/technology-partners/aws/",{"text":415,"config":416},"GitLab on Google Cloud",{"href":417,"dataGaName":415,"dataGaLocation":398},"/partners/technology-partners/google-cloud-platform/",{"text":419,"config":420},"Why GitLab?",{"href":87,"dataGaName":419,"dataGaLocation":398},{"freeTrial":422,"mobileIcon":427,"desktopIcon":432,"secondaryButton":435},{"text":423,"config":424},"Start free trial",{"href":425,"dataGaName":50,"dataGaLocation":426},"https://gitlab.com/-/trials/new/","nav",{"altText":428,"config":429},"Gitlab Icon",{"src":430,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":428,"config":433},{"src":434,"dataGaName":431,"dataGaLocation":426},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":436,"config":437},"Get Started",{"href":438,"dataGaName":439,"dataGaLocation":426},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":441,"mobileIcon":445,"desktopIcon":447},{"text":442,"config":443},"Learn more about GitLab Duo",{"href":79,"dataGaName":444,"dataGaLocation":426},"gitlab duo",{"altText":428,"config":446},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":448},{"src":434,"dataGaName":431,"dataGaLocation":426},{"freeTrial":450,"mobileIcon":455,"desktopIcon":457},{"text":451,"config":452},"Back to pricing",{"href":206,"dataGaName":453,"dataGaLocation":426,"icon":454},"back to pricing","GoBack",{"altText":428,"config":456},{"src":430,"dataGaName":431,"dataGaLocation":426},{"altText":428,"config":458},{"src":434,"dataGaName":431,"dataGaLocation":426},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":464,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"title":465,"button":466,"image":471,"config":475,"_id":477,"_type":31,"_source":33,"_file":478,"_stem":479,"_extension":36},"/shared/en-us/banner","is now in public beta!",{"text":467,"config":468},"Try the Beta",{"href":469,"dataGaName":470,"dataGaLocation":45},"/gitlab-duo/agent-platform/","duo banner",{"altText":472,"config":473},"GitLab Duo Agent Platform",{"src":474},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":476},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":481,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"data":482,"_id":686,"_type":31,"title":687,"_source":33,"_file":688,"_stem":689,"_extension":36},"/shared/en-us/main-footer",{"text":483,"source":484,"edit":490,"contribute":495,"config":500,"items":505,"minimal":678},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":485,"config":486},"View page source",{"href":487,"dataGaName":488,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":491,"config":492},"Edit this page",{"href":493,"dataGaName":494,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":496,"config":497},"Please contribute",{"href":498,"dataGaName":499,"dataGaLocation":489},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":501,"facebook":502,"youtube":503,"linkedin":504},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[506,529,585,614,648],{"title":63,"links":507,"subMenu":512},[508],{"text":509,"config":510},"DevSecOps platform",{"href":72,"dataGaName":511,"dataGaLocation":489},"devsecops platform",[513],{"title":204,"links":514},[515,519,524],{"text":516,"config":517},"View plans",{"href":206,"dataGaName":518,"dataGaLocation":489},"view plans",{"text":520,"config":521},"Why Premium?",{"href":522,"dataGaName":523,"dataGaLocation":489},"/pricing/premium/","why premium",{"text":525,"config":526},"Why Ultimate?",{"href":527,"dataGaName":528,"dataGaLocation":489},"/pricing/ultimate/","why ultimate",{"title":530,"links":531},"Solutions",[532,537,539,541,546,551,555,558,562,567,569,572,575,580],{"text":533,"config":534},"Digital transformation",{"href":535,"dataGaName":536,"dataGaLocation":489},"/topics/digital-transformation/","digital transformation",{"text":150,"config":538},{"href":152,"dataGaName":150,"dataGaLocation":489},{"text":139,"config":540},{"href":122,"dataGaName":123,"dataGaLocation":489},{"text":542,"config":543},"Agile development",{"href":544,"dataGaName":545,"dataGaLocation":489},"/solutions/agile-delivery/","agile delivery",{"text":547,"config":548},"Cloud transformation",{"href":549,"dataGaName":550,"dataGaLocation":489},"/topics/cloud-native/","cloud transformation",{"text":552,"config":553},"SCM",{"href":135,"dataGaName":554,"dataGaLocation":489},"source code management",{"text":23,"config":556},{"href":127,"dataGaName":557,"dataGaLocation":489},"continuous integration & delivery",{"text":559,"config":560},"Value stream management",{"href":179,"dataGaName":561,"dataGaLocation":489},"value stream management",{"text":563,"config":564},"GitOps",{"href":565,"dataGaName":566,"dataGaLocation":489},"/solutions/gitops/","gitops",{"text":189,"config":568},{"href":191,"dataGaName":192,"dataGaLocation":489},{"text":570,"config":571},"Small business",{"href":196,"dataGaName":197,"dataGaLocation":489},{"text":573,"config":574},"Public sector",{"href":201,"dataGaName":202,"dataGaLocation":489},{"text":576,"config":577},"Education",{"href":578,"dataGaName":579,"dataGaLocation":489},"/solutions/education/","education",{"text":581,"config":582},"Financial services",{"href":583,"dataGaName":584,"dataGaLocation":489},"/solutions/finance/","financial services",{"title":209,"links":586},[587,589,591,593,596,598,600,602,604,606,608,610,612],{"text":221,"config":588},{"href":223,"dataGaName":224,"dataGaLocation":489},{"text":226,"config":590},{"href":228,"dataGaName":229,"dataGaLocation":489},{"text":231,"config":592},{"href":233,"dataGaName":234,"dataGaLocation":489},{"text":236,"config":594},{"href":238,"dataGaName":595,"dataGaLocation":489},"docs",{"text":258,"config":597},{"href":260,"dataGaName":5,"dataGaLocation":489},{"text":253,"config":599},{"href":255,"dataGaName":256,"dataGaLocation":489},{"text":262,"config":601},{"href":264,"dataGaName":265,"dataGaLocation":489},{"text":275,"config":603},{"href":277,"dataGaName":278,"dataGaLocation":489},{"text":267,"config":605},{"href":269,"dataGaName":270,"dataGaLocation":489},{"text":280,"config":607},{"href":282,"dataGaName":283,"dataGaLocation":489},{"text":285,"config":609},{"href":287,"dataGaName":288,"dataGaLocation":489},{"text":290,"config":611},{"href":292,"dataGaName":293,"dataGaLocation":489},{"text":295,"config":613},{"href":297,"dataGaName":25,"dataGaLocation":489},{"title":312,"links":615},[616,618,620,622,624,626,628,632,637,639,641,643],{"text":319,"config":617},{"href":321,"dataGaName":314,"dataGaLocation":489},{"text":324,"config":619},{"href":326,"dataGaName":327,"dataGaLocation":489},{"text":332,"config":621},{"href":334,"dataGaName":335,"dataGaLocation":489},{"text":337,"config":623},{"href":339,"dataGaName":340,"dataGaLocation":489},{"text":342,"config":625},{"href":344,"dataGaName":345,"dataGaLocation":489},{"text":347,"config":627},{"href":349,"dataGaName":350,"dataGaLocation":489},{"text":629,"config":630},"Sustainability",{"href":631,"dataGaName":629,"dataGaLocation":489},"/sustainability/",{"text":633,"config":634},"Diversity, inclusion and belonging (DIB)",{"href":635,"dataGaName":636,"dataGaLocation":489},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":352,"config":638},{"href":354,"dataGaName":355,"dataGaLocation":489},{"text":362,"config":640},{"href":364,"dataGaName":365,"dataGaLocation":489},{"text":367,"config":642},{"href":369,"dataGaName":370,"dataGaLocation":489},{"text":644,"config":645},"Modern Slavery Transparency Statement",{"href":646,"dataGaName":647,"dataGaLocation":489},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"title":649,"links":650},"Contact Us",[651,654,656,658,663,668,673],{"text":652,"config":653},"Contact an expert",{"href":54,"dataGaName":55,"dataGaLocation":489},{"text":381,"config":655},{"href":383,"dataGaName":384,"dataGaLocation":489},{"text":386,"config":657},{"href":388,"dataGaName":389,"dataGaLocation":489},{"text":659,"config":660},"Status",{"href":661,"dataGaName":662,"dataGaLocation":489},"https://status.gitlab.com/","status",{"text":664,"config":665},"Terms of use",{"href":666,"dataGaName":667,"dataGaLocation":489},"/terms/","terms of use",{"text":669,"config":670},"Privacy statement",{"href":671,"dataGaName":672,"dataGaLocation":489},"/privacy/","privacy statement",{"text":674,"config":675},"Cookie preferences",{"dataGaName":676,"dataGaLocation":489,"id":677,"isOneTrustButton":108},"cookie preferences","ot-sdk-btn",{"items":679},[680,682,684],{"text":664,"config":681},{"href":666,"dataGaName":667,"dataGaLocation":489},{"text":669,"config":683},{"href":671,"dataGaName":672,"dataGaLocation":489},{"text":674,"config":685},{"dataGaName":676,"dataGaLocation":489,"id":677,"isOneTrustButton":108},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[691],{"_path":692,"_dir":693,"_draft":6,"_partial":6,"_locale":7,"content":694,"config":699,"_id":701,"_type":31,"title":702,"_source":33,"_file":703,"_stem":704,"_extension":36},"/en-us/blog/authors/josh-kodroff-pulumi","authors",{"role":695,"name":18,"config":696},"Sr. Solutions Architect, Pulumi",{"headshot":697,"ctfId":698},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749683425/Blog/Author%20Headshots/joshkodroff.jpg","2GF0MF1ngEBxos4nRKt8tL",{"template":700},"BlogAuthor","content:en-us:blog:authors:josh-kodroff-pulumi.yml","Josh Kodroff Pulumi","en-us/blog/authors/josh-kodroff-pulumi.yml","en-us/blog/authors/josh-kodroff-pulumi",{"_path":706,"_dir":39,"_draft":6,"_partial":6,"_locale":7,"header":707,"eyebrow":708,"blurb":709,"button":710,"secondaryButton":714,"_id":716,"_type":31,"title":717,"_source":33,"_file":718,"_stem":719,"_extension":36},"/shared/en-us/next-steps","Start shipping better software faster","50%+ of the Fortune 100 trust GitLab","See what your team can do with the intelligent\n\n\nDevSecOps platform.\n",{"text":47,"config":711},{"href":712,"dataGaName":50,"dataGaLocation":713},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":52,"config":715},{"href":54,"dataGaName":55,"dataGaLocation":713},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",1758326225312]