Skip to content

Git on AWS after CodeCommit: Gitea, Bitbucket DC, GHES, and the Actions pattern

Post-CodeCommit, Git-on-AWS means self-hosting Gitea, Bitbucket Data Center, or GitHub Enterprise Server in a VPC — or accepting GitHub.com with OIDC. Trade-offs for residency-bound workloads.

A regulated customer asks the question their auditor is going to ask: “where does the source code live, and who can read it?” CodeCommit’s answer was clean — inside the customer’s AWS account, IAM-controlled, CloudTrail-audited. With CodeCommit closed to new customers and softly on the deprecation path, that clean answer is gone. The replacements are four shapes: self-hosted Gitea on EC2 or ECS, Bitbucket Data Center inside a VPC, GitHub Enterprise Server (also inside a VPC), or GitHub.com with OIDC and a careful read of where the audit perimeter actually lives. Each has a different residency story, a different operational cost, and a different developer experience. The right answer depends on which trade-offs the contract allows.

What’s happening

CodeCommit’s design value was a control plane and a data plane that both lived inside the customer’s AWS account. The post-CodeCommit landscape splits those two: either you keep the data inside an AWS account by self-hosting a Git server, or you accept that the source-of-truth tenant moves to a SaaS provider and use OIDC / careful auditing to maintain control without owning the data plane.

Gitea on EC2 or ECS. Gitea is open-source, Go-binary-small, runs on a single EC2 instance with a PostgreSQL RDS backend or in a container on ECS Fargate. The full data plane is inside your VPC. The trade-off is the developer experience — Gitea’s PR review is competent but not GitHub-equivalent (no merge queue, no CODEOWNERS at GitHub’s depth, no Copilot, no Marketplace of Actions). For a small team that values residency over polish, it’s the closest CodeCommit replacement.

Bitbucket Data Center. Atlassian’s self-hosted offering, deployed onto an EC2 cluster or EKS with a PostgreSQL RDS backend. The PR review is closer to GitHub-level (Atlassian has invested in code review tooling for years). It integrates with Bamboo, Jenkins, and increasingly with native Pipelines runners. The trade-off is licensing cost (per-user, paid upfront — pricing model only, not a number to publish), operational complexity (clustering, Elasticsearch sidecar, scheduled maintenance), and a smaller third-party plugin ecosystem than GitHub’s.

GitHub Enterprise Server (GHES). GitHub’s self-hosted appliance, deployed as a VM image on EC2 (or AWS Marketplace AMI). Full GitHub feature set — Actions, Packages, the PR review experience — but in a VM you control. The trade-off is the operational footprint: GHES is a substantial appliance, with its own update cadence, its own performance characteristics (it needs real IOPS), and a licensing model that’s per-user with a non-trivial minimum. For organizations that want GitHub’s UX without the SaaS tenancy, it’s the answer; for small teams, it’s overkill.

GitHub.com with OIDC. The pragmatic answer for most teams: accept that source code lives in GitHub’s tenant, audit access via GitHub’s audit log API exported to your S3 / CloudTrail Lake, and use OIDC for Actions-to-AWS auth so no long-lived AWS credentials exist outside AWS. The data plane (the repo content) is in GitHub. The control plane (who can read it, who can push, what branches are protected) is GitHub’s enterprise admin UI, mirrored into your SIEM via the audit log stream. The trade-off is that your contract with GitHub becomes part of the residency story — and for customers with hard data-location constraints (Canadian data must stay in Canada), GitHub.com’s primary regions are not Canadian.

The “Actions pattern that replaces CodePipeline triggers” is the migration glue across all four. CodePipeline fires on a CodeCommit push event by polling or via CloudWatch Events. Post-migration, the equivalent is a GitHub Actions workflow that uses aws-actions/configure-aws-credentials with OIDC, assumes an IAM role in the target AWS account, and either drives a deploy directly (CDK, Terraform, aws s3 sync) or kicks the existing CodePipeline by calling codepipeline:StartPipelineExecution. The latter pattern preserves your existing CodePipeline investment — you just replace the trigger surface, not the pipeline.

How to verify

For each replacement option, three checks determine feasibility on a specific workload.

# 1. Data residency: what region must source live in?
# Check the customer's contract and any active DPAs.
# CodeCommit was in us-east-1 / ca-central-1; if the contract requires ca-central-1,
# the SaaS options are constrained:
#  - GitHub.com: primary in US; data residency add-on can pin to EU; no Canadian region
#  - Self-hosted in ca-central-1: any of Gitea/Bitbucket DC/GHES works

# 2. CodePipeline footprint: how many pipelines must be re-wired?
aws codepipeline list-pipelines --query 'pipelines[].name' --output text | \
  while read p; do
    aws codepipeline get-pipeline --name "$p" \
      --query 'pipeline.stages[?actions[?actionTypeId.provider==`CodeCommit`]].name' \
      --output text
  done | grep -v '^$' | wc -l

A high count means the migration includes re-wiring N pipelines; that cost is the same regardless of which Git server you land on, so it doesn’t influence the choice. The choice is driven by residency and developer experience.

# 3. Developer expectations: what features are non-negotiable?
# Survey questions to ask the team:
#  - Is Copilot already in IDE expectations? (Yes → GHES or GitHub.com)
#  - Do PRs need merge queue? (Yes → GHES or GitHub.com)
#  - Is the team willing to operate a self-hosted appliance? (No → GitHub.com)
#  - Do auditors require the data plane inside the customer's tenant? (Yes → self-hosted)

The survey is non-quantitative but the answers determine the option. Skipping it leads to picking on infrastructure preference and discovering six months later that the team is unhappy.

The fix

The decision flow:

  1. Establish the residency floor. If the contract or regulator requires Canadian data residency at the source-code level, the GitHub.com option is out — and the residency add-ons for Enterprise Cloud are EU-only as of writing, not Canadian. The choice reduces to self-hosted in ca-central-1: Gitea, Bitbucket DC, or GHES. If residency is flexible (most non-regulated SaaS is), all four options are available.

  2. Match the developer expectation surface. A team that lives in GitHub.com for open-source work will be unhappy on Gitea. A team that doesn’t use Copilot, doesn’t write Actions, and treats the Git server as a place to push code will be fine on Gitea. Bitbucket DC is the right choice when the team is already deep in Atlassian (Jira, Confluence) — the cross-product integration is the value, not the Git UX.

  3. Pick the operational fit. Each self-hosted option has a different ops profile:

    • Gitea: the lightest. Single binary, single PostgreSQL. Upgrades are a binary swap. SSO via OAuth/OIDC. Good fit for a small ops team. Capacity for hundreds of devs is achievable on a single right-sized EC2 instance plus RDS.
    • Bitbucket DC: clustered (HA requires at least two nodes plus shared filer for repo storage — typically EFS), Elasticsearch sidecar, scheduled major version upgrades. Atlassian’s published deployment patterns are mature. Real operational discipline required.
    • GHES: appliance with its own update cycle (monthly minor, quarterly patch). Backups are GitHub-native (ghe-backup ssh-based), restore tested in DR. Disk IOPS matter — undersized disk is the most common GHES outage. The appliance is opinionated; you don’t tune it the way you tune Gitea.
  4. Wire the Actions-replacing-CodePipeline-triggers pattern. Across all options, the pattern is the same:

# .github/workflows/deploy.yml  (or .gitea/workflows/deploy.yml, etc.)
name: deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions:
      id-token: write   # required for OIDC
      contents: read
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActions-Deploy
          aws-region: ca-central-1
      - name: Trigger existing CodePipeline
        run: |
          aws codepipeline start-pipeline-execution \
            --name production-deploy

The IAM role’s trust policy allows sts:AssumeRoleWithWebIdentity from the GitHub OIDC provider, scoped to the specific repo and branch. No long-lived AWS keys land in GitHub Secrets. The existing CodePipeline stays — its trigger source moved from CodeCommit to “manual via GitHub Actions.”

  1. Keep the audit perimeter explicit. For GitHub.com / GHES, configure the audit log streaming endpoint to deliver into S3 (and from there into CloudTrail Lake or your SIEM). For Bitbucket DC, the audit log is in PostgreSQL — ship it via Logstash or Fluent Bit. For Gitea, the same — Gitea’s audit events go to its log file, ship them. The CodeCommit equivalent (CloudTrail events on codecommit:* actions) needs an explicit replacement, not an assumption that the new platform’s defaults cover it.

  2. Plan the migration runbook for each repo. The mechanical migration is the same across destinations — the mirror-push and validation steps don’t change. Only the destination URL and the trigger-replacement details differ.

Two rules we hold:

Don’t pick on cost alone. The cost difference between GitHub.com Enterprise Cloud and a self-hosted GHES cluster is meaningful, but the operational cost of a self-hosted appliance for a small team usually erases that gap. The right comparison is total cost of ownership across two years, not list price.

Don’t conflate “self-hosted” with “more secure.” A well-configured GitHub.com Enterprise instance with audit log streaming, SAML SSO, and OIDC-to-AWS is in many cases better secured than a self-hosted appliance run by a small ops team that misses a patch cycle. The residency question is real; the security question is mostly about the operating discipline applied, not the deployment shape.

The post-CodeCommit Git-on-AWS landscape is healthy — every option works, the trade-offs are real, and the migration patterns are well-trodden. The decision is owning the trade-off, not finding a perfect replacement. There isn’t one.


Stack Harbor runs CodePipeline + CodeCommit (and the GitHub-on-AWS variants) as part of Managed AWS onboarding and ongoing Managed Cloud Ops. If you’re sizing a Git-on-AWS replacement against a real residency constraint, start with an infrastructure consult.