[{"data":1,"prerenderedAt":761},["ShallowReactive",2],{"/en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration":3,"navigation-en-us":42,"banner-en-us":466,"footer-en-us":483,"Tim Rizzi":727,"next-steps-en-us":740,"footer-source-/en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration/":755},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":8,"content":16,"config":31,"_id":35,"_type":36,"title":37,"_source":38,"_file":39,"_stem":40,"_extension":41},"/en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration","blog",false,"",{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},"Secure and publish Python packages: A guide to CI integration","Learn how to implement a secure CI/CD pipeline across five stages with the GitLab DevSecOps platform.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1749662080/Blog/Hero%20Images/AdobeStock_1097303277.jpg","https://about.gitlab.com/blog/secure-and-publish-python-packages-a-guide-to-ci-integration","https://about.gitlab.com","article","\n                        {\n        \"@context\": \"https://schema.org\",\n        \"@type\": \"Article\",\n        \"headline\": \"Secure and publish Python packages: A guide to CI integration\",\n        \"author\": [{\"@type\":\"Person\",\"name\":\"Tim Rizzi\"}],\n        \"datePublished\": \"2025-01-21\",\n      }",{"title":9,"description":10,"authors":17,"heroImage":11,"date":19,"body":20,"category":21,"tags":22},[18],"Tim Rizzi","2025-01-21","Supply chain security is a critical concern in software development.\nOrganizations need to verify the authenticity and integrity of their\nsoftware packages. This guide will show you how to implement a secure CI/CD\npipeline for Python packages using GitLab CI, incorporating package signing\nand attestation using Sigstore's Cosign.\n\n\nYou'll learn:\n\n\n- [Why sign and attest your Python\npackages?](#why-sign-and-attest-your-python-packages%3F)\n\n- [Pipeline overview](#pipeline-overview)\n\n- [Complete pipeline implementation: Setting up the\nenvironment](#complete-pipeline-implementation-setting-up-the-environment)\n   * [Environment configuration](#environment-configuration)\n   * [Configuration breakdown](#configuration-breakdown)\n-  The 6 stages\n\n    1. [Building](#building-crafting-the-package)\n    2. [Signing](#signing-the-digital-notarization)\n    3. [Verification](#verification-the-security-checkpoint)\n    4. [Publishing](#publishing-the-controlled-release)\n    5. [Publishing signatures](#publishing-signatures-making-verification-possible)\n    6. [Consumer verification](#consumer-verification-testing-the-user-experience)\n\n## Why sign and attest your Python packages?\n\n\nHere are four reasons to sign and attest your Python packages:\n\n\n* **Supply chain security:** Package signing ensures that the code hasn't\nbeen tampered with between build and deployment, protecting against supply\nchain attacks.\n\n* **Compliance requirements:** Many organizations, especially in regulated\nindustries, require cryptographic signatures and provenance information for\nall deployed software.\n\n* **Traceability:** Attestations provide a verifiable record of build\nconditions, including who built the package and under what circumstances.\n\n* **Trust verification:** Consumers of your package can cryptographically\nverify its authenticity before installation.\n\n\n## Pipeline overview\n\n\nEnsuring your code's integrity and authenticity is necessary. Imagine a\npipeline that doesn't just compile your code but creates a cryptographically\nverifiable narrative of how, when, and by whom your package was created.\nEach stage acts as a guardian, checking and documenting the package's\nprovenance.\n\n\nHere are six stages of a GitLab pipeline that ensure your package is secure\nand trustworthy:\n\n\n* Build: Creates a clean, standard package that can be easily shared and\ninstalled.\n\n* Signing: Adds a digital signature that proves the package hasn't been\ntampered with since it was created.\n\n* Verification: Double-checks that the signature is valid and the package\nmeets all our security requirements.\n\n* Publishing: Uploads the verified package to GitLab's package registry,\nmaking it available for others to use.\n\n* Publishing Signatures: Makes signatures available for verification.\n\n* Consumer Verification: Simulates how end users can verify package\nauthenticity.\n\n\n## Complete pipeline implementation: Setting up the environment\n\n\nBefore we build our package, we need to set up a consistent and secure build\nenvironment. This configuration ensures every package is created with the\nsame tools, settings, and security checks.\n\n\n### Environment configuration\n\n\nOur pipeline requires specific tools and settings to work correctly.\n\n\nPrimary configurations:\n\n\n* Python 3.10 for consistent builds\n\n* Cosign 2.2.3 for package signing\n\n* GitLab package registry integration\n\n* Hardcoded package version for reproducibility\n\n\n**Note about versioning:** We've chosen to use a hardcoded version\n(`\"1.0.0\"`) in this example rather than deriving it from git tags or\ncommits. This approach ensures complete reproducibility and makes the\npipeline behavior more predictable. In a production environment, you might\nwant to use semantic versioning based on git tags or another versioning\nstrategy that fits your release process.\n\n\nTool requirements:\n\n\n* Basic utilities: `curl`, `wget`\n\n* Cosign for cryptographic signing\n\n* Python packaging tools: `build`, `twine`, `setuptools`, `wheel`\n\n\n### Configuration breakdown\n\n\n```yaml\n\nvariables:\n  PYTHON_VERSION: '3.10'\n  PACKAGE_NAME: ${CI_PROJECT_NAME}\n  PACKAGE_VERSION: \"1.0.0\"\n  FULCIO_URL: 'https://fulcio.sigstore.dev'\n  REKOR_URL: 'https://rekor.sigstore.dev'\n  CERTIFICATE_IDENTITY: 'https://gitlab.com/${CI_PROJECT_PATH}//.gitlab-ci.yml@refs/heads/${CI_DEFAULT_BRANCH}'\n  CERTIFICATE_OIDC_ISSUER: 'https://gitlab.com'\n  PIP_CACHE_DIR: \"$CI_PROJECT_DIR/.pip-cache\"\n  COSIGN_YES: \"true\"\n  GENERIC_PACKAGE_BASE_URL: \"${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/generic/${PACKAGE_NAME}/${PACKAGE_VERSION}\"\n```\n\n\nWe use caching to speed up subsequent builds:\n\n\n```yaml\n\ncache:\n  paths:\n    - ${PIP_CACHE_DIR}\n```\n\n\n## Building: Crafting the package\n\n\nEvery software journey begins with creation. In our pipeline, the build\nstage is where raw code transforms into a distributable package, ready to\ntravel across different Python environments.\n\n\nThe build process creates two standardized formats:\n\n\n* a wheel package (.whl) for quick, efficient installation\n\n* a source distribution (.tar.gz) that carries the complete code\n\n\nHere's the build stage implementation:\n\n\n```yaml\n\nbuild:\n  extends: .python-job\n  stage: build\n  script:\n    - git init\n    - git config --global init.defaultBranch main\n    - git config --global user.email \"ci@example.com\"\n    - git config --global user.name \"CI\"\n    - git add .\n    - git commit -m \"Initial commit\"\n    - export NORMALIZED_NAME=$(echo \"${CI_PROJECT_NAME}\" | tr '-' '_')\n    - sed -i \"s/name = \\\".*\\\"/name = \\\"${NORMALIZED_NAME}\\\"/\" pyproject.toml\n    - sed -i \"s|\\\"Homepage\\\" = \\\".*\\\"|\\\"Homepage\\\" = \\\"https://gitlab.com/${CI_PROJECT_PATH}\\\"|\" pyproject.toml\n    - python -m build\n  artifacts:\n    paths:\n      - dist/\n      - pyproject.toml\n```\n\n\nLet's break down what this build stage does:\n\n\n1. Initializes a Git repository (`git init`) and configures it with basic\nsettings\n\n2. Normalizes the package name by converting hyphens to underscores, which\nis required for Python packaging\n\n3. Updates the package metadata in `pyproject.toml` to match our project\nsettings\n\n4. Builds both wheel and source distribution packages using `python -m\nbuild`\n\n5. Preserves the built packages and configuration as artifacts for\nsubsequent stages\n\n\n## Signing: The digital notarization\n\n\nIf attestation is the package's biography, signing is its cryptographic seal\nof authenticity. This is where we transform our package from a mere\ncollection of files into a verified, tamper-evident artifact.\n\n\nThe signing stage uses Cosign to apply a digital signature as an unbreakable\nseal. This isn't just a stamp — it's a complex cryptographic handshake that\nproves the package's integrity and origin.\n\n\n```yaml\n\nsign:\n  extends: .python+cosign-job\n  stage: sign\n  id_tokens:\n    SIGSTORE_ID_TOKEN:\n      aud: sigstore\n  script:\n    - |\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          cosign sign-blob --yes \\\n            --fulcio-url=${FULCIO_URL} \\\n            --rekor-url=${REKOR_URL} \\\n            --oidc-issuer $CI_SERVER_URL \\\n            --identity-token $SIGSTORE_ID_TOKEN \\\n            --output-signature \"dist/${filename}.sig\" \\\n            --output-certificate \"dist/${filename}.crt\" \\\n            \"$file\"\n        fi\n      done\n  artifacts:\n    paths:\n      - dist/\n```\n\n\nThis signing stage performs several crucial operations:\n\n\n1. Obtains an OIDC token from GitLab for authentication with Sigstore\nservices\n\n2. Processes each built package (both wheel and source distribution)\n\n3. Uses Cosign to create a cryptographic signature (`.sig`) for each package\n\n4. Generates a certificate (`.crt`) that proves the signature's authenticity\n\n5. Stores both signatures and certificates alongside the packages as\nartifacts\n\n\n## Verification: The security checkpoint\n\n\nVerification is our final quality control gate. It's not just a check — it's\na security interrogation where every aspect of the package is scrutinized.\n\n\n```yaml\n\nverify:\n  extends: .python+cosign-job\n  stage: verify\n  script:\n    - |\n      failed=0\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          if ! cosign verify-blob \\\n            --signature \"dist/${filename}.sig\" \\\n            --certificate \"dist/${filename}.crt\" \\\n            --certificate-identity \"${CERTIFICATE_IDENTITY}\" \\\n            --certificate-oidc-issuer \"${CERTIFICATE_OIDC_ISSUER}\" \\\n            \"$file\"; then\n            failed=1\n          fi\n        fi\n      done\n      if [ $failed -eq 1 ]; then\n        exit 1\n      fi\n```\n\n\nThe verification stage implements several security checks:\n\n\n1. Examines each package file in the `dist` directory\n\n2. Uses Cosign to verify the signature matches the package content\n\n3. Confirms the certificate's identity matches our expected GitLab pipeline\nidentity\n\n4. Validates our trusted OIDC provider issued the certificate\n\n5. Fails the entire pipeline if any verification check fails, ensuring only\nverified packages proceed\n\n\n## Publishing: The controlled release\n\n\nPublishing is where we make our verified packages available through GitLab's\npackage registry. It's a carefully choreographed release that ensures only\nverified, authenticated packages reach their destination.\n\n\n```yaml\n\npublish:\n  extends: .python-job\n  stage: publish\n  script:\n    - |\n      cat \u003C\u003C EOF > ~/.pypirc\n      [distutils]\n      index-servers = gitlab\n      [gitlab]\n      repository = ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi\n      username = gitlab-ci-token\n      password = ${CI_JOB_TOKEN}\n      EOF\n      TWINE_PASSWORD=${CI_JOB_TOKEN} TWINE_USERNAME=gitlab-ci-token \\\n        twine upload --repository-url ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/packages/pypi \\\n        dist/*.whl dist/*.tar.gz\n```\n\n\nThe publishing stage handles several important tasks:\n\n\n1. Creates a `.pypirc` configuration file with GitLab package registry\ncredentials\n\n2. Uses the GitLab CI job token for secure authentication\n\n3. Uploads both wheel and source distribution packages to the GitLab PyPI\nregistry\n\n4. Makes the packages available for installation via pip\n\n\n## Publishing signatures: Making verification possible\n\n\nAfter publishing the packages, we must make their signatures and\ncertificates available for verification. We store these in GitLab's generic\npackage registry, making them easily accessible to users who want to verify\npackage authenticity.\n\n\n```yaml\n\npublish_signatures:\n  extends: .python+cosign-job\n  stage: publish_signatures\n  script:\n    - |\n      for file in dist/*.whl dist/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          curl --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --fail \\\n               --upload-file \"dist/${filename}.sig\" \\\n               \"${GENERIC_PACKAGE_BASE_URL}/${filename}.sig\"\n\n          curl --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --fail \\\n               --upload-file \"dist/${filename}.crt\" \\\n               \"${GENERIC_PACKAGE_BASE_URL}/${filename}.crt\"\n        fi\n      done\n```\n\n\nThe signature publishing stage performs these key operations:\n\n\n1. Processes each built package to find its corresponding signature files\n\n2. Uses the GitLab API to upload the signature (`.sig`) file to the generic\npackage registry\n\n3. Uploads the corresponding certificate (`.crt`) file\n\n4. Makes these verification artifacts available for downstream package\nconsumers\n\n5. Uses the same version and package name to maintain the connection between\npackages and signatures\n\n\n## Consumer verification: Testing the user experience\n\n\nThe final stage simulates how end users will verify your package's\nauthenticity. This stage acts as a final check and a practical example of\nthe verification process.\n\n\n```yaml\n\nconsumer_verification:\n  extends: .python+cosign-job\n  stage: consumer_verification\n  script:\n    - |\n      git init\n      git config --global init.defaultBranch main\n      mkdir -p pkg signatures\n\n      pip download --index-url \"https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple\" \\\n          \"${NORMALIZED_NAME}==${PACKAGE_VERSION}\" --no-deps -d ./pkg\n\n      pip download --no-binary :all: \\\n          --index-url \"https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/api/v4/projects/${CI_PROJECT_ID}/packages/pypi/simple\" \\\n          \"${NORMALIZED_NAME}==${PACKAGE_VERSION}\" --no-deps -d ./pkg\n\n      failed=0\n      for file in pkg/*.whl pkg/*.tar.gz; do\n        if [ -f \"$file\" ]; then\n          filename=$(basename \"$file\")\n          sig_url=\"${GENERIC_PACKAGE_BASE_URL}/${filename}.sig\"\n          cert_url=\"${GENERIC_PACKAGE_BASE_URL}/${filename}.crt\"\n\n          curl --fail --silent --show-error \\\n               --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --output \"signatures/${filename}.sig\" \\\n               \"$sig_url\"\n\n          curl --fail --silent --show-error \\\n               --header \"JOB-TOKEN: ${CI_JOB_TOKEN}\" \\\n               --output \"signatures/${filename}.crt\" \\\n               \"$cert_url\"\n\n          if ! cosign verify-blob \\\n            --signature \"signatures/${filename}.sig\" \\\n            --certificate \"signatures/${filename}.crt\" \\\n            --certificate-identity \"${CERTIFICATE_IDENTITY}\" \\\n            --certificate-oidc-issuer \"${CERTIFICATE_OIDC_ISSUER}\" \\\n            \"$file\"; then\n            failed=1\n          fi\n        fi\n      done\n\n      if [ $failed -eq 1 ]; then\n        exit 1\n      fi\n```\n\n\nThis consumer verification stage simulates the end-user experience by:\n\n\n1. Creating a clean environment to test package installation\n\n2. Downloading the published packages from the GitLab PyPI registry\n\n3. Retrieving the corresponding signatures and certificates from the generic\npackage registry\n\n4. Performing the same verification steps that end users would perform\n\n5. Ensuring the entire process works from a consumer's perspective\n\n6. Failing the pipeline if any verification step fails, providing an early\nwarning of any issues\n\n\n## Summary\n\n\nThis comprehensive pipeline provides a secure and reliable way to build,\nsign, and publish Python packages to GitLab's package registry. By following\nthese practices and implementing the suggested security measures, you can\nensure your packages are appropriately verified and safely distributed to\nyour users.\n\n\nThe pipeline combines modern security practices with efficient automation to\ncreate a robust software supply chain. Using Sigstore's Cosign for signing\nand attestation, along with GitLab's built-in security features, you can\nprovide users with trustworthy cryptographically verified packages.\n\n\n> #### Get started on your security journey today with a [free trial\nof GitLab\nUltimate](https://gitlab.com/-/trials/new?glm_content=default-saas-trial&glm_source=about.gitlab.com).\n\n\n## Learn more\n\n- [Documentation: Use Sigstore for keyless signing and\nverification](https://docs.gitlab.com/ee/ci/yaml/signing_examples.html)\n\n- [Streamline security with keyless signing and verification in\nGitLab](https://about.gitlab.com/blog/keyless-signing-with-cosign/)\n\n- [Annotate container images with build provenance using Cosign in GitLab\nCI/CD](https://about.gitlab.com/blog/annotate-container-images-with-build-provenance-using-cosign-in-gitlab-ci-cd/)\n","security",[21,23,24,25,26,27,28,29,30],"integrations","partners","features","CI","CI/CD","DevSecOps platform","tutorial","solutions architecture",{"slug":32,"featured":33,"template":34},"secure-and-publish-python-packages-a-guide-to-ci-integration",true,"BlogPost","content:en-us:blog:secure-and-publish-python-packages-a-guide-to-ci-integration.yml","yaml","Secure And Publish Python Packages A Guide To Ci Integration","content","en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration.yml","en-us/blog/secure-and-publish-python-packages-a-guide-to-ci-integration","yml",{"_path":43,"_dir":44,"_draft":6,"_partial":6,"_locale":7,"data":45,"_id":462,"_type":36,"title":463,"_source":38,"_file":464,"_stem":465,"_extension":41},"/shared/en-us/main-navigation","en-us",{"logo":46,"freeTrial":51,"sales":56,"login":61,"items":66,"search":393,"minimal":424,"duo":443,"pricingDeployment":452},{"config":47},{"href":48,"dataGaName":49,"dataGaLocation":50},"/","gitlab logo","header",{"text":52,"config":53},"Get free trial",{"href":54,"dataGaName":55,"dataGaLocation":50},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":57,"config":58},"Talk to sales",{"href":59,"dataGaName":60,"dataGaLocation":50},"/sales/","sales",{"text":62,"config":63},"Sign in",{"href":64,"dataGaName":65,"dataGaLocation":50},"https://gitlab.com/users/sign_in/","sign in",[67,111,206,211,314,374],{"text":68,"config":69,"cards":71,"footer":94},"Platform",{"dataNavLevelOne":70},"platform",[72,78,86],{"title":68,"description":73,"link":74},"The most comprehensive AI-powered DevSecOps Platform",{"text":75,"config":76},"Explore our Platform",{"href":77,"dataGaName":70,"dataGaLocation":50},"/platform/",{"title":79,"description":80,"link":81},"GitLab Duo (AI)","Build software faster with AI at every stage of development",{"text":82,"config":83},"Meet GitLab Duo",{"href":84,"dataGaName":85,"dataGaLocation":50},"/gitlab-duo/","gitlab duo ai",{"title":87,"description":88,"link":89},"Why GitLab","10 reasons why Enterprises choose GitLab",{"text":90,"config":91},"Learn more",{"href":92,"dataGaName":93,"dataGaLocation":50},"/why-gitlab/","why gitlab",{"title":95,"items":96},"Get started with",[97,102,107],{"text":98,"config":99},"Platform Engineering",{"href":100,"dataGaName":101,"dataGaLocation":50},"/solutions/platform-engineering/","platform engineering",{"text":103,"config":104},"Developer Experience",{"href":105,"dataGaName":106,"dataGaLocation":50},"/developer-experience/","Developer experience",{"text":108,"config":109},"MLOps",{"href":110,"dataGaName":108,"dataGaLocation":50},"/topics/devops/the-role-of-ai-in-devops/",{"text":112,"left":33,"config":113,"link":115,"lists":119,"footer":188},"Product",{"dataNavLevelOne":114},"solutions",{"text":116,"config":117},"View all Solutions",{"href":118,"dataGaName":114,"dataGaLocation":50},"/solutions/",[120,144,167],{"title":121,"description":122,"link":123,"items":128},"Automation","CI/CD and automation to accelerate deployment",{"config":124},{"icon":125,"href":126,"dataGaName":127,"dataGaLocation":50},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[129,132,136,140],{"text":27,"config":130},{"href":131,"dataGaLocation":50,"dataGaName":27},"/solutions/continuous-integration/",{"text":133,"config":134},"AI-Assisted Development",{"href":84,"dataGaLocation":50,"dataGaName":135},"AI assisted development",{"text":137,"config":138},"Source Code Management",{"href":139,"dataGaLocation":50,"dataGaName":137},"/solutions/source-code-management/",{"text":141,"config":142},"Automated Software Delivery",{"href":126,"dataGaLocation":50,"dataGaName":143},"Automated software delivery",{"title":145,"description":146,"link":147,"items":152},"Security","Deliver code faster without compromising security",{"config":148},{"href":149,"dataGaName":150,"dataGaLocation":50,"icon":151},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[153,157,162],{"text":154,"config":155},"Application Security Testing",{"href":149,"dataGaName":156,"dataGaLocation":50},"Application security testing",{"text":158,"config":159},"Software Supply Chain Security",{"href":160,"dataGaLocation":50,"dataGaName":161},"/solutions/supply-chain/","Software supply chain security",{"text":163,"config":164},"Software Compliance",{"href":165,"dataGaName":166,"dataGaLocation":50},"/solutions/software-compliance/","software compliance",{"title":168,"link":169,"items":174},"Measurement",{"config":170},{"icon":171,"href":172,"dataGaName":173,"dataGaLocation":50},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[175,179,183],{"text":176,"config":177},"Visibility & Measurement",{"href":172,"dataGaLocation":50,"dataGaName":178},"Visibility and Measurement",{"text":180,"config":181},"Value Stream Management",{"href":182,"dataGaLocation":50,"dataGaName":180},"/solutions/value-stream-management/",{"text":184,"config":185},"Analytics & Insights",{"href":186,"dataGaLocation":50,"dataGaName":187},"/solutions/analytics-and-insights/","Analytics and insights",{"title":189,"items":190},"GitLab for",[191,196,201],{"text":192,"config":193},"Enterprise",{"href":194,"dataGaLocation":50,"dataGaName":195},"/enterprise/","enterprise",{"text":197,"config":198},"Small Business",{"href":199,"dataGaLocation":50,"dataGaName":200},"/small-business/","small business",{"text":202,"config":203},"Public Sector",{"href":204,"dataGaLocation":50,"dataGaName":205},"/solutions/public-sector/","public sector",{"text":207,"config":208},"Pricing",{"href":209,"dataGaName":210,"dataGaLocation":50,"dataNavLevelOne":210},"/pricing/","pricing",{"text":212,"config":213,"link":215,"lists":219,"feature":301},"Resources",{"dataNavLevelOne":214},"resources",{"text":216,"config":217},"View all resources",{"href":218,"dataGaName":214,"dataGaLocation":50},"/resources/",[220,252,274],{"title":221,"items":222},"Getting started",[223,228,233,238,243,248],{"text":224,"config":225},"Install",{"href":226,"dataGaName":227,"dataGaLocation":50},"/install/","install",{"text":229,"config":230},"Quick start guides",{"href":231,"dataGaName":232,"dataGaLocation":50},"/get-started/","quick setup checklists",{"text":234,"config":235},"Learn",{"href":236,"dataGaLocation":50,"dataGaName":237},"https://university.gitlab.com/","learn",{"text":239,"config":240},"Product documentation",{"href":241,"dataGaName":242,"dataGaLocation":50},"https://docs.gitlab.com/","product documentation",{"text":244,"config":245},"Best practice videos",{"href":246,"dataGaName":247,"dataGaLocation":50},"/getting-started-videos/","best practice videos",{"text":249,"config":250},"Integrations",{"href":251,"dataGaName":23,"dataGaLocation":50},"/integrations/",{"title":253,"items":254},"Discover",[255,260,264,269],{"text":256,"config":257},"Customer success stories",{"href":258,"dataGaName":259,"dataGaLocation":50},"/customers/","customer success stories",{"text":261,"config":262},"Blog",{"href":263,"dataGaName":5,"dataGaLocation":50},"/blog/",{"text":265,"config":266},"Remote",{"href":267,"dataGaName":268,"dataGaLocation":50},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"text":270,"config":271},"TeamOps",{"href":272,"dataGaName":273,"dataGaLocation":50},"/teamops/","teamops",{"title":275,"items":276},"Connect",[277,282,287,292,297],{"text":278,"config":279},"GitLab Services",{"href":280,"dataGaName":281,"dataGaLocation":50},"/services/","services",{"text":283,"config":284},"Community",{"href":285,"dataGaName":286,"dataGaLocation":50},"/community/","community",{"text":288,"config":289},"Forum",{"href":290,"dataGaName":291,"dataGaLocation":50},"https://forum.gitlab.com/","forum",{"text":293,"config":294},"Events",{"href":295,"dataGaName":296,"dataGaLocation":50},"/events/","events",{"text":298,"config":299},"Partners",{"href":300,"dataGaName":24,"dataGaLocation":50},"/partners/",{"backgroundColor":302,"textColor":303,"text":304,"image":305,"link":309},"#2f2a6b","#fff","Insights for the future of software development",{"altText":306,"config":307},"the source promo card",{"src":308},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":310,"config":311},"Read the latest",{"href":312,"dataGaName":313,"dataGaLocation":50},"/the-source/","the source",{"text":315,"config":316,"lists":318},"Company",{"dataNavLevelOne":317},"company",[319],{"items":320},[321,326,332,334,339,344,349,354,359,364,369],{"text":322,"config":323},"About",{"href":324,"dataGaName":325,"dataGaLocation":50},"/company/","about",{"text":327,"config":328,"footerGa":331},"Jobs",{"href":329,"dataGaName":330,"dataGaLocation":50},"/jobs/","jobs",{"dataGaName":330},{"text":293,"config":333},{"href":295,"dataGaName":296,"dataGaLocation":50},{"text":335,"config":336},"Leadership",{"href":337,"dataGaName":338,"dataGaLocation":50},"/company/team/e-group/","leadership",{"text":340,"config":341},"Team",{"href":342,"dataGaName":343,"dataGaLocation":50},"/company/team/","team",{"text":345,"config":346},"Handbook",{"href":347,"dataGaName":348,"dataGaLocation":50},"https://handbook.gitlab.com/","handbook",{"text":350,"config":351},"Investor relations",{"href":352,"dataGaName":353,"dataGaLocation":50},"https://ir.gitlab.com/","investor relations",{"text":355,"config":356},"Trust Center",{"href":357,"dataGaName":358,"dataGaLocation":50},"/security/","trust center",{"text":360,"config":361},"AI Transparency Center",{"href":362,"dataGaName":363,"dataGaLocation":50},"/ai-transparency-center/","ai transparency center",{"text":365,"config":366},"Newsletter",{"href":367,"dataGaName":368,"dataGaLocation":50},"/company/contact/","newsletter",{"text":370,"config":371},"Press",{"href":372,"dataGaName":373,"dataGaLocation":50},"/press/","press",{"text":375,"config":376,"lists":377},"Contact us",{"dataNavLevelOne":317},[378],{"items":379},[380,383,388],{"text":57,"config":381},{"href":59,"dataGaName":382,"dataGaLocation":50},"talk to sales",{"text":384,"config":385},"Support portal",{"href":386,"dataGaName":387,"dataGaLocation":50},"https://support.gitlab.com","support portal",{"text":389,"config":390},"Customer portal",{"href":391,"dataGaName":392,"dataGaLocation":50},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":394,"login":395,"suggestions":402},"Close",{"text":396,"link":397},"To search repositories and projects, login to",{"text":398,"config":399},"gitlab.com",{"href":64,"dataGaName":400,"dataGaLocation":401},"search login","search",{"text":403,"default":404},"Suggestions",[405,407,411,413,417,421],{"text":79,"config":406},{"href":84,"dataGaName":79,"dataGaLocation":401},{"text":408,"config":409},"Code Suggestions (AI)",{"href":410,"dataGaName":408,"dataGaLocation":401},"/solutions/code-suggestions/",{"text":27,"config":412},{"href":131,"dataGaName":27,"dataGaLocation":401},{"text":414,"config":415},"GitLab on AWS",{"href":416,"dataGaName":414,"dataGaLocation":401},"/partners/technology-partners/aws/",{"text":418,"config":419},"GitLab on Google Cloud",{"href":420,"dataGaName":418,"dataGaLocation":401},"/partners/technology-partners/google-cloud-platform/",{"text":422,"config":423},"Why GitLab?",{"href":92,"dataGaName":422,"dataGaLocation":401},{"freeTrial":425,"mobileIcon":430,"desktopIcon":435,"secondaryButton":438},{"text":426,"config":427},"Start free trial",{"href":428,"dataGaName":55,"dataGaLocation":429},"https://gitlab.com/-/trials/new/","nav",{"altText":431,"config":432},"Gitlab Icon",{"src":433,"dataGaName":434,"dataGaLocation":429},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":431,"config":436},{"src":437,"dataGaName":434,"dataGaLocation":429},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":439,"config":440},"Get Started",{"href":441,"dataGaName":442,"dataGaLocation":429},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/compare/gitlab-vs-github/","get started",{"freeTrial":444,"mobileIcon":448,"desktopIcon":450},{"text":445,"config":446},"Learn more about GitLab Duo",{"href":84,"dataGaName":447,"dataGaLocation":429},"gitlab duo",{"altText":431,"config":449},{"src":433,"dataGaName":434,"dataGaLocation":429},{"altText":431,"config":451},{"src":437,"dataGaName":434,"dataGaLocation":429},{"freeTrial":453,"mobileIcon":458,"desktopIcon":460},{"text":454,"config":455},"Back to pricing",{"href":209,"dataGaName":456,"dataGaLocation":429,"icon":457},"back to pricing","GoBack",{"altText":431,"config":459},{"src":433,"dataGaName":434,"dataGaLocation":429},{"altText":431,"config":461},{"src":437,"dataGaName":434,"dataGaLocation":429},"content:shared:en-us:main-navigation.yml","Main Navigation","shared/en-us/main-navigation.yml","shared/en-us/main-navigation",{"_path":467,"_dir":44,"_draft":6,"_partial":6,"_locale":7,"title":468,"button":469,"image":474,"config":478,"_id":480,"_type":36,"_source":38,"_file":481,"_stem":482,"_extension":41},"/shared/en-us/banner","is now in public beta!",{"text":470,"config":471},"Try the Beta",{"href":472,"dataGaName":473,"dataGaLocation":50},"/gitlab-duo/agent-platform/","duo banner",{"altText":475,"config":476},"GitLab Duo Agent Platform",{"src":477},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1753720689/somrf9zaunk0xlt7ne4x.svg",{"layout":479},"release","content:shared:en-us:banner.yml","shared/en-us/banner.yml","shared/en-us/banner",{"_path":484,"_dir":44,"_draft":6,"_partial":6,"_locale":7,"data":485,"_id":723,"_type":36,"title":724,"_source":38,"_file":725,"_stem":726,"_extension":41},"/shared/en-us/main-footer",{"text":486,"source":487,"edit":493,"contribute":498,"config":503,"items":508,"minimal":715},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":488,"config":489},"View page source",{"href":490,"dataGaName":491,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":494,"config":495},"Edit this page",{"href":496,"dataGaName":497,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":499,"config":500},"Please contribute",{"href":501,"dataGaName":502,"dataGaLocation":492},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":504,"facebook":505,"youtube":506,"linkedin":507},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[509,556,608,652,681],{"title":207,"links":510,"subMenu":525},[511,515,520],{"text":512,"config":513},"View plans",{"href":209,"dataGaName":514,"dataGaLocation":492},"view plans",{"text":516,"config":517},"Why Premium?",{"href":518,"dataGaName":519,"dataGaLocation":492},"/pricing/premium/","why premium",{"text":521,"config":522},"Why Ultimate?",{"href":523,"dataGaName":524,"dataGaLocation":492},"/pricing/ultimate/","why ultimate",[526],{"title":527,"links":528},"Contact Us",[529,532,534,536,541,546,551],{"text":530,"config":531},"Contact sales",{"href":59,"dataGaName":60,"dataGaLocation":492},{"text":384,"config":533},{"href":386,"dataGaName":387,"dataGaLocation":492},{"text":389,"config":535},{"href":391,"dataGaName":392,"dataGaLocation":492},{"text":537,"config":538},"Status",{"href":539,"dataGaName":540,"dataGaLocation":492},"https://status.gitlab.com/","status",{"text":542,"config":543},"Terms of use",{"href":544,"dataGaName":545,"dataGaLocation":492},"/terms/","terms of use",{"text":547,"config":548},"Privacy statement",{"href":549,"dataGaName":550,"dataGaLocation":492},"/privacy/","privacy statement",{"text":552,"config":553},"Cookie preferences",{"dataGaName":554,"dataGaLocation":492,"id":555,"isOneTrustButton":33},"cookie preferences","ot-sdk-btn",{"title":112,"links":557,"subMenu":564},[558,561],{"text":28,"config":559},{"href":77,"dataGaName":560,"dataGaLocation":492},"devsecops platform",{"text":133,"config":562},{"href":84,"dataGaName":563,"dataGaLocation":492},"ai-assisted development",[565],{"title":566,"links":567},"Topics",[568,573,578,583,588,593,598,603],{"text":569,"config":570},"CICD",{"href":571,"dataGaName":572,"dataGaLocation":492},"/topics/ci-cd/","cicd",{"text":574,"config":575},"GitOps",{"href":576,"dataGaName":577,"dataGaLocation":492},"/topics/gitops/","gitops",{"text":579,"config":580},"DevOps",{"href":581,"dataGaName":582,"dataGaLocation":492},"/topics/devops/","devops",{"text":584,"config":585},"Version Control",{"href":586,"dataGaName":587,"dataGaLocation":492},"/topics/version-control/","version control",{"text":589,"config":590},"DevSecOps",{"href":591,"dataGaName":592,"dataGaLocation":492},"/topics/devsecops/","devsecops",{"text":594,"config":595},"Cloud Native",{"href":596,"dataGaName":597,"dataGaLocation":492},"/topics/cloud-native/","cloud native",{"text":599,"config":600},"AI for Coding",{"href":601,"dataGaName":602,"dataGaLocation":492},"/topics/devops/ai-for-coding/","ai for coding",{"text":604,"config":605},"Agentic AI",{"href":606,"dataGaName":607,"dataGaLocation":492},"/topics/agentic-ai/","agentic ai",{"title":609,"links":610},"Solutions",[611,613,615,620,624,627,631,634,636,639,642,647],{"text":154,"config":612},{"href":149,"dataGaName":154,"dataGaLocation":492},{"text":143,"config":614},{"href":126,"dataGaName":127,"dataGaLocation":492},{"text":616,"config":617},"Agile development",{"href":618,"dataGaName":619,"dataGaLocation":492},"/solutions/agile-delivery/","agile delivery",{"text":621,"config":622},"SCM",{"href":139,"dataGaName":623,"dataGaLocation":492},"source code management",{"text":569,"config":625},{"href":131,"dataGaName":626,"dataGaLocation":492},"continuous integration & delivery",{"text":628,"config":629},"Value stream management",{"href":182,"dataGaName":630,"dataGaLocation":492},"value stream management",{"text":574,"config":632},{"href":633,"dataGaName":577,"dataGaLocation":492},"/solutions/gitops/",{"text":192,"config":635},{"href":194,"dataGaName":195,"dataGaLocation":492},{"text":637,"config":638},"Small business",{"href":199,"dataGaName":200,"dataGaLocation":492},{"text":640,"config":641},"Public sector",{"href":204,"dataGaName":205,"dataGaLocation":492},{"text":643,"config":644},"Education",{"href":645,"dataGaName":646,"dataGaLocation":492},"/solutions/education/","education",{"text":648,"config":649},"Financial services",{"href":650,"dataGaName":651,"dataGaLocation":492},"/solutions/finance/","financial services",{"title":212,"links":653},[654,656,658,660,663,665,667,669,671,673,675,677,679],{"text":224,"config":655},{"href":226,"dataGaName":227,"dataGaLocation":492},{"text":229,"config":657},{"href":231,"dataGaName":232,"dataGaLocation":492},{"text":234,"config":659},{"href":236,"dataGaName":237,"dataGaLocation":492},{"text":239,"config":661},{"href":241,"dataGaName":662,"dataGaLocation":492},"docs",{"text":261,"config":664},{"href":263,"dataGaName":5,"dataGaLocation":492},{"text":256,"config":666},{"href":258,"dataGaName":259,"dataGaLocation":492},{"text":265,"config":668},{"href":267,"dataGaName":268,"dataGaLocation":492},{"text":278,"config":670},{"href":280,"dataGaName":281,"dataGaLocation":492},{"text":270,"config":672},{"href":272,"dataGaName":273,"dataGaLocation":492},{"text":283,"config":674},{"href":285,"dataGaName":286,"dataGaLocation":492},{"text":288,"config":676},{"href":290,"dataGaName":291,"dataGaLocation":492},{"text":293,"config":678},{"href":295,"dataGaName":296,"dataGaLocation":492},{"text":298,"config":680},{"href":300,"dataGaName":24,"dataGaLocation":492},{"title":315,"links":682},[683,685,687,689,691,693,695,699,704,706,708,710],{"text":322,"config":684},{"href":324,"dataGaName":317,"dataGaLocation":492},{"text":327,"config":686},{"href":329,"dataGaName":330,"dataGaLocation":492},{"text":335,"config":688},{"href":337,"dataGaName":338,"dataGaLocation":492},{"text":340,"config":690},{"href":342,"dataGaName":343,"dataGaLocation":492},{"text":345,"config":692},{"href":347,"dataGaName":348,"dataGaLocation":492},{"text":350,"config":694},{"href":352,"dataGaName":353,"dataGaLocation":492},{"text":696,"config":697},"Sustainability",{"href":698,"dataGaName":696,"dataGaLocation":492},"/sustainability/",{"text":700,"config":701},"Diversity, inclusion and belonging (DIB)",{"href":702,"dataGaName":703,"dataGaLocation":492},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":355,"config":705},{"href":357,"dataGaName":358,"dataGaLocation":492},{"text":365,"config":707},{"href":367,"dataGaName":368,"dataGaLocation":492},{"text":370,"config":709},{"href":372,"dataGaName":373,"dataGaLocation":492},{"text":711,"config":712},"Modern Slavery Transparency Statement",{"href":713,"dataGaName":714,"dataGaLocation":492},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":716},[717,719,721],{"text":542,"config":718},{"href":544,"dataGaName":545,"dataGaLocation":492},{"text":547,"config":720},{"href":549,"dataGaName":550,"dataGaLocation":492},{"text":552,"config":722},{"dataGaName":554,"dataGaLocation":492,"id":555,"isOneTrustButton":33},"content:shared:en-us:main-footer.yml","Main Footer","shared/en-us/main-footer.yml","shared/en-us/main-footer",[728],{"_path":729,"_dir":730,"_draft":6,"_partial":6,"_locale":7,"content":731,"config":735,"_id":737,"_type":36,"title":18,"_source":38,"_file":738,"_stem":739,"_extension":41},"/en-us/blog/authors/tim-rizzi","authors",{"name":18,"config":732},{"headshot":733,"ctfId":734},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749661866/Blog/Author%20Headshots/trizzi-headshot.jpg","trizzi",{"template":736},"BlogAuthor","content:en-us:blog:authors:tim-rizzi.yml","en-us/blog/authors/tim-rizzi.yml","en-us/blog/authors/tim-rizzi",{"_path":741,"_dir":44,"_draft":6,"_partial":6,"_locale":7,"header":742,"eyebrow":743,"blurb":744,"button":745,"secondaryButton":749,"_id":751,"_type":36,"title":752,"_source":38,"_file":753,"_stem":754,"_extension":41},"/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":52,"config":746},{"href":747,"dataGaName":55,"dataGaLocation":748},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":57,"config":750},{"href":59,"dataGaName":60,"dataGaLocation":748},"content:shared:en-us:next-steps.yml","Next Steps","shared/en-us/next-steps.yml","shared/en-us/next-steps",{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"seo":756,"content":757,"config":760,"_id":35,"_type":36,"title":37,"_source":38,"_file":39,"_stem":40,"_extension":41},{"title":9,"description":10,"ogTitle":9,"ogDescription":10,"noIndex":6,"ogImage":11,"ogUrl":12,"ogSiteName":13,"ogType":14,"canonicalUrls":12,"schema":15},{"title":9,"description":10,"authors":758,"heroImage":11,"date":19,"body":20,"category":21,"tags":759},[18],[21,23,24,25,26,27,28,29,30],{"slug":32,"featured":33,"template":34},1761814419290]