Deploying self-managed GitLab on AWS: when a single instance is enough and when it is not
Choose a single EC2 instance only if the instance can be rebuilt and restored within the outage you accept, and move to a multi-component design once you need separate scaling, managed services or disaster recovery. A platform or security lead should decide this on three inputs: expected user and CI load, how long the business can tolerate GitLab being unavailable, and who will operate the system after go-live. Deploying self-managed GitLab on AWS means your team runs GitLab in your own AWS accounts, so the instance, its storage, backups, upgrades and the runner fleet all become your responsibility or that of the partner you engage.
Edition and tier choice comes before architecture. GitLab Free costs 0 US dollars and is available for self-managed installations. Premium costs 29 US dollars per user per month billed annually and adds advanced CI/CD and priority support. Ultimate is custom priced and adds application security testing, software supply chain security, vulnerability management and compliance features. Confirm the tier you need with GitLab on the GitLab pricing page before sizing, because the scanning and compliance features you require change both licensing cost and the number of supporting services you deploy.
A single instance built from the community images is a reasonable starting point for a small team, a pilot or a non-production environment, but it concentrates the database, Git storage and background jobs on one host. GitLab's reference architectures begin at 1,000 users and 20 API requests per second, and GitLab generally recommends a high availability strategy at 3,000 or more users. At that scale, plan for a separate PostgreSQL service such as Amazon RDS, managed Redis, S3 object storage and a Gitaly layout, and confirm each service version against the release you intend to run. Also confirm that stateful components will run on VMs or managed services, because the reference architectures do not support running PostgreSQL or Redis inside Kubernetes.
Which AWS layout fits which stage
This table compares three ways to run self-managed GitLab on AWS, from a single EC2 instance built from community AMIs to a reference architecture with clustered stateful services. Read each row across to see the fit, which components sit on VMs or managed services, and the main trade-off before choosing a starting point. Stateful components such as PostgreSQL and Redis cannot run inside Kubernetes in any of these layouts, so the choice is about VMs and managed services.
| Layout | Stateful components | Main trade-off |
|---|---|---|
| Single EC2 instance from community AMIs. Fit: evaluation, pilots and small teams that accept a single point of failure. | GitLab's bundled PostgreSQL, Redis and Gitaly run on the same EC2 instance and its attached volumes. | Lowest setup effort, but one instance failure takes down source control, reviews and CI/CD until restored from backup. |
| EC2 with RDS PostgreSQL and S3 object storage. Fit: a single GitLab application node with external database and object storage for a growing team. | PostgreSQL on Amazon RDS, object storage on S3, while Gitaly and Redis remain on the GitLab VM. | Less database operations work, but the application node is still a single point of failure and Gitaly storage needs its own backup plan. |
| Reference architecture with Gitaly Cluster, Patroni-managed PostgreSQL, Redis Sentinel and Consul on VMs. Fit: organizations at the scale the published reference architectures target, often 3,000 or more users. | Gitaly Cluster with Praefect, PostgreSQL with Patroni, Redis Sentinel and Consul, all on EC2 VMs or other VM-based services. | Highest availability and the most components to size, patch, monitor and upgrade. Confirm the exact sizing with the reference architecture docs before committing. |
How to set up GitLab on AWS in order
This sequence takes a self-managed GitLab instance from sizing decisions to a tested, backed-up platform with runners attached. Each step closes with a check so your team knows when to move on and when to stop.
Confirm sizing and user count
Start by matching your expected user count and API request rate to the published reference architecture ranges, which run from 1,000 users (20 API requests per second) to 50,000 users (1,000 API requests per second). Decide whether a single node is enough or whether you need the high availability pattern, which the docs generally recommend for 3,000 or more users. Record your choice, the expected CI job load and the GitLab release you plan to install, because every later decision depends on them. Confirm the current reference architecture details and the supported PostgreSQL and Redis versions for that release with GitLab before you purchase anything. Done when you have a written sizing note that names the reference architecture, the GitLab release and the open questions for your team.
Design the network and VPC
Define the VPC, subnets, route tables, security groups and DNS names that the GitLab host, the managed database, the cache and the object storage endpoint will use. Keep the database and Redis in private subnets with no public access, and limit who can reach the GitLab web and SSH ports. Plan how runners will reach GitLab and how users will reach it, including whether the instance sits behind a load balancer and which TLS certificates you will use. Your network team or cloud architect should own this design, and your security team should review ingress rules. Done when a diagram and rule set exist, and a test instance in the subnet can resolve and reach the planned database and cache endpoints.
Provision compute and storage
Launch the EC2 instance or instances at the size your sizing note calls for, and attach encrypted block storage with enough capacity for repositories, logs and temporary files. Avoid burstable instance families for production GitLab, as the hardware guidance discourages them. Confirm the instance type, operating system and disk layout against the server requirements for your chosen release. Apply your standard hardening baseline and time synchronization before installing anything. Done when the instances are reachable through your approved access path, disks are mounted and encrypted, and the operating system matches the release requirements.
Install GitLab from the Linux package or community AMI
Choose between the Linux package on a host you built and a GitLab community AMI, then install the exact release you selected in step one. The Linux package is the most mature installation method and bundles PostgreSQL, Redis and Sidekiq, but in an AWS production layout you will point it at external services instead of the bundled ones. Confirm that the AMI and the package version match the target release, since AMI availability and naming can change, and record the image identifier. Set the external URL and initial administrator access in gitlab.rb, then run the reconfigure step. Done when the GitLab sign-in page loads over HTTPS and the administrator can sign in and see the version under the admin area.
Connect RDS PostgreSQL and Redis
Create the managed PostgreSQL database and the Redis service in the private subnets, then configure GitLab to use them as external services. Confirm the PostgreSQL major version against the GitLab release requirements and the extensions and admin roles GitLab expects on a managed service. Set the connection, credentials and TLS settings in gitlab.rb, and disable the bundled PostgreSQL and Redis on the application node. Reconfigure and restart GitLab, then run the built-in health checks. Done when GitLab reports healthy database and Redis connections, migrations complete without errors, and a test project can be created and pushed to.
Configure S3 object storage
Create S3 buckets or prefixes for the data types GitLab stores outside Git repositories, such as artifacts, LFS objects, uploads, packages and the container registry if you use it. Attach an IAM role to the instance with only the permissions those buckets need, and configure GitLab's object storage settings for each data type. Enable encryption on the buckets and restrict public access. Confirm which data types need separate settings in the release you installed, because the consolidated form does not cover every type. Done when a test CI job uploads an artifact, a test upload to a project succeeds, and the objects appear in the expected bucket.
Run backup and restore tests
Run a full backup with GitLab's backup task, including the configuration and secrets files that the backup task does not capture by default, and store the output in a separate location with its own access controls. Restore it to a second, isolated instance built from the same release and confirm that projects, merge requests, users and object storage content come back. Record how long the backup and restore took on your data so you can plan future windows. Document the steps and who runs them. Done when a restore to a clean environment reproduces a working instance and the runbook names the people responsible for each step.
Connect GitLab Runner
Install GitLab Runner on separate hosts or autoscaling capacity, keeping its major and minor versions aligned with the GitLab release, and register it with the instance using a scoped token. Choose an executor that matches your isolation requirements, such as Docker or an autoscaling executor on cloud VMs, and keep runners out of the GitLab application subnet. Restrict which projects and tags can use each runner. Done when a test pipeline runs on the new runner, the job log appears in GitLab, and the runner shows as online in the admin area.
How the install method and the tier decide the AWS build
The install method sets the shape of the AWS build before any instance is sized. GitLab documents five routes: the Linux package, the Helm chart, the GitLab Operator, Docker and installation from source. GitLab describes the Linux package as the most mature and scalable of these, and it bundles PostgreSQL, Redis and Sidekiq, so a single instance is self-contained by default. The Helm chart and Operator run on Kubernetes, which moves the build toward Amazon EKS and away from the EC2 layout this page covers. For an EC2 build, the practical decision is whether to keep the bundled services on the instance or point GitLab at Amazon RDS for PostgreSQL and a managed Redis service. Read the installation methods page and confirm which PostgreSQL and Redis versions your target release supports before you commit to a managed service.
The tier decides which features exist on the instance, and therefore which AWS services you need to plan. Free has no license cost for self-managed installations, Premium is listed at 29 US dollars per user per month billed annually, and Ultimate is custom priced. Premium adds advanced CI/CD and priority support, along with Geo, which provides a warm-standby secondary site in another region for disaster recovery. If Geo is in scope, the AWS build needs a second region, a database replication path and object storage in both places, so that choice changes the architecture as well as the license bill. Ultimate adds application security testing, software supply chain security, vulnerability management and compliance features, which increase the load on runners and scanning jobs that your capacity plan has to cover.
Single instance versus multi-node is the largest cost decision in the build. A single EC2 instance with the bundled services is the simplest starting point on AWS and suits a small team with modest CI load. The reference architectures begin at 1,000 users and 20 API requests per second, and GitLab generally recommends a high availability strategy at 3,000 or more users, which adds Gitaly Cluster with Praefect, PostgreSQL with Patroni, Redis Sentinel and Consul. Cost comes from four places: the EC2, storage and load balancing resources you run, the managed database and Redis services, the subscription for the chosen tier, and the labor to build, back up and upgrade the system. The subscription is the only line priced per user. Ask any partner for a quote that separates these four lines, marks which are one-time and which recur, and shows the required upgrade stops built into the year.
Security controls to plan on AWS
These controls decide how exposed a self-managed GitLab instance is on AWS. Each one touches a different layer: identity, network, data encryption, secrets, configuration and CI/CD execution. Plan them together before the first install so that later hardening is a tuning exercise rather than a rebuild.
IAM roles for S3 access
GitLab writes backups, artifacts, LFS objects and other data to S3 buckets, so the instance needs permission to reach those buckets and nothing else. Attach an instance profile scoped to the specific bucket names and prefixes GitLab uses, rather than broad S3 permissions on the account. Ask your team to confirm which actions the object storage configuration requires for each data type, and review the policy whenever you add a new bucket. Check that no long-lived access keys are stored in gitlab.rb.
Private subnets and security groups
Place the GitLab application host, the database and the Redis endpoint in private subnets, and expose only the HTTPS and SSH ports users need through a load balancer or controlled entry point. Restrict each security group to the ports and source groups that the component requires, so the database accepts connections only from the GitLab host. Confirm that runners and other CI systems reach GitLab through an approved path, and that outbound traffic from the instance is limited to the destinations your policy allows.
Encryption for RDS and S3
Enable encryption at rest on the RDS PostgreSQL instance and on the S3 buckets that hold repository-adjacent data such as backups and artifacts. Decide whether AWS-managed keys are sufficient or whether your organization requires customer-managed KMS keys, and ask your security team to confirm that answer before the build. Also confirm that connections from GitLab to RDS and to S3 use TLS, and record the key policies so that restores in a second account or region remain possible.
Secrets in gitlab.rb and database credentials
The configuration file gitlab.rb and the secrets file hold database passwords, signing keys and encryption keys that protect the instance. Keep these values out of version control and out of shared chat, restrict file permissions on the host, and store the secrets file in a backup location separate from the application data. Plan how database credentials are rotated and how the secrets file is restored, because losing it can make encrypted data unreadable after a restore.
Hardening guidance and gradual rollout
GitLab's hardening documentation covers application settings, CI/CD, configuration files, the operating system and mapping to NIST SP 800-53 controls. GitLab describes that guidance as a work in progress to be rolled out gradually, so treat it as a checklist to adopt in stages rather than a single change window. Map each recommendation to a control your assessor expects, record which items you accepted, deferred or rejected and why, and re-check the list at each upgrade.
Runner isolation on AWS
CI/CD jobs run arbitrary code from your repositories, so runners are a separate security boundary from the GitLab application. Decide where runners run, which instance role they receive, and whether jobs on shared runners can reach the GitLab database or the S3 buckets. Use separate runner fleets for sensitive projects, ephemeral instances where the autoscaling executor allows it, and no shared credentials between the runner host and the application. Confirm this design with your security team before enabling shared runners.
Keeping an AWS GitLab current with upgrade stops and tested backups
GitLab ships a minor release on the third Thursday of each month and patch releases twice a month, and it ships one major release a year. The GitLab maintenance policy currently maintains 19.5, 19.4 and 19.3, and it backports bug fixes only to the current stable release. Security fixes also go to the previous two monthly releases, so a stack that falls several minors behind loses coverage fast. Plan a standing window for patch releases on your EC2 or reference architecture nodes, and plan a separate project for each major release. Upgrades cannot skip required upgrade stops, which since GitLab 17.5 fall at versions x.2, x.5, x.8 and x.11, such as 18.2, 18.5, 18.8, 18.11, 19.2, 19.5, 19.8 and 19.11. Map your current version to the next stop before you schedule any upgrade, and confirm the path against the GitLab upgrade paths documentation for your install method.
An AWS snapshot of the instance or EBS volume does not replace GitLab backup and restore. The GitLab backup task captures the database, repositories, uploads and the object data that lives outside the instance, and it does not capture the /etc/gitlab/gitlab-secrets.json file, which holds the keys needed to read encrypted data. Keep that file and gitlab.rb in a separate, access-controlled store, and confirm the backup configuration covers each object storage bucket you use. If PostgreSQL runs on RDS, RDS automated backups and snapshots cover the database, but you still need a restore test that brings GitLab back to a consistent state with its repositories and S3 objects. Confirm with your team which recovery point and recovery time you can accept, and run a full restore into a separate environment on a schedule. Ask the RDS and S3 configuration owners to document retention and versioning settings before go-live.
Write the restore test into the upgrade plan rather than treating it as a separate project. Take a fresh backup before each required stop, record the GitLab version and the schema state, and restore that backup to a staging copy built from the same AMI or Linux package version. Run a restore check after each stop, not only at the final version, because a failed stop is easier to recover from when the last good backup is recent. Document who can approve a rollback, which backup is the rollback point, and how long a restore takes in your environment, using measured numbers from your own test rather than vendor figures. The GitLab backup and restore documentation lists the components a restore must rebuild, and your test should check each one.
Questions buyers ask before deploying GitLab on AWS
What drives the cost and effort of deploying self-managed GitLab on AWS?
The main drivers are the target architecture, the number of users and CI load, and how much of the platform your team already runs. A single EC2 instance has few moving parts, while a layout with RDS for PostgreSQL, managed Redis, S3 object storage, Gitaly storage and multiple application nodes adds components to design, secure, back up and monitor. Cost falls into two groups. One-time costs cover planning, build, integration, migration and testing. Recurring costs cover AWS resources, GitLab subscription fees (GitLab Free is free, Premium is listed at 29 US dollars per user per month billed annually, per the GitLab pricing page), runner capacity, backups and upgrades. Ask any quote to separate AWS infrastructure from licensing and labor, and to state which tier it assumes.
How long does a single-instance GitLab build take compared with a reference-style build?
Duration depends on the architecture, the number of integrations, identity and network requirements, and how much data must be migrated. A single instance built from GitLab's community AMIs has fewer steps than a layout with separate PostgreSQL, Redis, object storage and multiple application nodes, which also needs load testing, failover checks and documented restore procedures. No duration is stated here because a fixed figure would depend on your environment. Ask for a timeline in the written scope that lists each phase, the environment prerequisites, the client decisions required before build starts, and the acceptance tests that close the work.
What does the work need from our team?
The work needs named owners for AWS accounts, networking, IAM, DNS and TLS certificates, identity (SAML, LDAP or OAuth 2.0 providers), backup retention and on-call response. Your team should also decide who approves production changes, which users and groups receive access, and where logs and metrics go. Expect to provide sample repositories and pipelines for validation, a maintenance window for cutover, and staff who can test restores. Confirm these responsibilities in the written scope so that gaps are visible before the build begins, not during cutover.
Can our internal team deploy and run GitLab on AWS without a partner?
Many teams can, provided they have experience with Linux administration, AWS networking and IAM, PostgreSQL operations, and the GitLab upgrade process. The harder parts are usually not the first install but the recurring work: backups that have been restored in a test, upgrade stops followed on schedule, runner fleets kept in version alignment with GitLab, and hardening applied gradually. Assess your team honestly against those tasks. A partner can be engaged for the build only, for the build plus handover documentation, or for ongoing support under a retainer. DeployOpen plans, implements, documents and supports self-hosted platforms inside your own AWS accounts.
How do required upgrade stops affect the GitLab design on AWS?
Upgrades must pass through required upgrade stops, which since GitLab 17.5 fall at versions x.2, x.5, x.8 and x.11, for example 18.2, 18.5, 18.8, 18.11, 19.2, 19.5, 19.8 and 19.11, according to GitLab's upgrade paths documentation. Design decisions that affect this include how the database is provisioned and backed up before each stop, whether RDS and object storage versions still match the target release, and whether you have a staging environment that can rehearse each step. Security fixes are backported to the previous two monthly releases, while bug fixes go only to the current stable release, so a planned cadence matters. Ask for a written upgrade runbook as a deliverable.
When do we need GitLab Geo for disaster recovery on AWS?
Geo is needed when a warm-standby secondary site in another region, continuously synchronized, is a recovery requirement that backups alone cannot meet. GitLab Geo is available in the Premium and Ultimate tiers for self-managed GitLab, according to the GitLab Geo documentation. A secondary site doubles infrastructure and adds replication monitoring, failover testing and operational runbooks, so it should follow a documented recovery time and recovery point decision from your business continuity owner. For many teams, a tested backup and restore plan in a second region is the first step. Ask for a Geo option to be priced separately in the quote.
What should a fixed-scope quote for this work include?
A fixed-scope quote should list the architecture, the AWS services and sizing assumptions, the GitLab edition and tier, the number of users and expected CI load, and the integrations in scope such as identity, runners and object storage. It should name each deliverable (infrastructure code, configuration, runner setup, backup and restore tests, hardening steps, documentation and handover), the acceptance criteria, and the client responsibilities listed above. It should separate one-time build work from recurring support or upgrade retainers, and state what is excluded, such as data migration from other hosts or compliance evidence. Quotes follow from the written scope, and DeployOpen does not publish a price list.
How DeployOpen scopes and delivers GitLab on AWS
DeployOpen plans, implements, hardens, backs up, documents and supports self-hosted open-source platforms inside the client's own AWS accounts. For GitLab, we start from the topology you need, then build, test and hand over the instance, its database, object storage, runners and recovery procedures in your environment.
How we scope
- Written scope fixes the GitLab edition, user count, topology and which AWS services are in and out of the work
- Scope names the single-instance or reference architecture choice and the RDS PostgreSQL, Redis and S3 layout
- Scope lists runner needs, identity integration, backup targets and the upgrade stops planned for the release line
- Engagements are fixed-scope, so deliverables and timelines are agreed in writing before any build starts
How we build
- Install the Linux package or AMI-based instance in your AWS account, with network, IAM and encryption settings agreed beforehand
- Connect GitLab to RDS PostgreSQL, managed Redis and S3 object storage, then verify each connection and data path
- Configure runners, identity provider sign-in and CI/CD settings, testing each against the written acceptance criteria
- Apply hardening settings in stages, recording each change in configuration files such as gitlab.rb for review
How we hand over
- Deliver architecture diagrams, configuration records and access model documentation covering every AWS component
- Provide backup and restore runbooks, with a test restore performed and its results recorded before handover
- Document the upgrade procedure around required upgrade stops, maintenance windows and rollback steps for your team
- Walk your operators through monitoring, health checks and the day-two tasks they will own after go-live
How we support
- Ongoing support, upgrades and continuous testing are available as retainers, scoped separately from the build
- Retainer scope can cover upgrade planning across required stops, patch application and runner version alignment
- Ask the quote to separate one-time build costs from recurring AWS charges and any support retainer
- Confirm with your team which GitLab edition, user counts and compliance evidence needs will drive the ongoing scope

