Installing a GitLab POC on Amazon Web Services (AWS)

  • Tier: Free, Premium, Ultimate
  • Offering: GitLab Self-Managed

This page offers a walkthrough of a common configuration for GitLab on AWS using the official Linux package. You should customize it to accommodate your needs.

For organizations with 1,000 users or less, the recommended AWS installation method is to launch an EC2 single box Linux package installation and implement a snapshot strategy for backing up the data. See the 20 RPS or 1,000 user reference architecture for more information.

Getting started for production-grade GitLab

This document is an installation guide for a proof of concept instance. It is not a reference architecture, and it does not result in a highly available configuration. It’s highly recommended to use the GitLab Environment Toolkit (GET) instead.

Following this guide exactly results in a proof of concept instance that roughly equates to a scaled down version of a two availability zone implementation of the Non-HA 40 RPS or 2,000 User Reference Architecture. The 2K reference architecture is not HA because it is primarily intended to provide some scaling while keeping costs and complexity low. The 60 RPS or 3,000 User Reference Architecture is the smallest size that is GitLab HA. It has additional service roles to achieve HA, most notably it uses Gitaly Cluster (Praefect) to achieve HA for Git repository storage and specifies triple redundancy.

GitLab maintains and tests two main types of Reference Architectures. The Linux package architectures are implemented on instance compute while Cloud Native Hybrid architectures maximize the use of a Kubernetes cluster. Cloud Native Hybrid reference architecture specifications are addendum sections to the Reference Architecture size pages that start by describing the Linux package architecture. For example, the 60 RPS or 3,000 User Cloud Native Reference Architecture is in the subsection titled Cloud Native Hybrid reference architecture with Helm Charts (alternative) in the 60 RPS or 3,000 User Reference Architecture page.

Getting started for production-grade Linux package installations

The Infrastructure as Code tooling GitLab Environment Tool (GET) is the best place to start for building using the Linux package on AWS and most especially if you are targeting an HA setup. While it does not automate everything, it does complete complex setups like Gitaly Cluster (Praefect) for you. GET is open source so anyone can build on top of it and contribute improvements to it.

Getting started for production-grade Cloud Native Hybrid GitLab

The GitLab Environment Toolkit (GET) is a set of opinionated Terraform and Ansible scripts. These scripts help with the deployment of Linux package or Cloud Native Hybrid environments on selected cloud providers and are used by GitLab developers for GitLab Dedicated (for example).

You can use the GitLab Environment Toolkit to deploy a Cloud Native Hybrid environment on AWS. However, it’s not required and may not support every valid permutation. That said, the scripts are presented as-is and you can adapt them accordingly.

Introduction

For the most part, we make use of the Linux package in our setup, but we also leverage native AWS services. Instead of using the Linux package-bundled PostgreSQL and Redis, we use Amazon RDS and ElastiCache.

In this guide, we go through a multi-node setup where we start by configuring our Virtual Private Cloud and subnets to later integrate services such as RDS for our database server and ElastiCache as a Redis cluster to finally manage them in an auto scaling group with custom scaling policies.

Requirements

In addition to having a basic familiarity with AWS and Amazon EC2, you need:

It can take a few hours to validate a certificate provisioned through ACM. To avoid delays later, request your certificate as soon as possible.

Architecture

The following diagram outlines the recommended architecture.

A scaled down 2 Availability Zone and Non-HA AWS architecture.

AWS costs

GitLab uses the following AWS services, with links to pricing information:

  • EC2: GitLab is deployed on shared hardware, for which on-demand pricing applies. If you want to run GitLab on a dedicated or reserved instance, see the EC2 pricing page for information about its cost.
  • S3: GitLab uses S3 (pricing page) to store backups, artifacts, and LFS objects.
  • NLB: A Network Load Balancer (pricing page), used to route requests to the GitLab instances.
  • RDS: An Amazon Relational Database Service using PostgreSQL (pricing page).
  • ElastiCache: An in-memory cache environment (pricing page), used to provide a Redis configuration.

Create an IAM EC2 instance role and profile

As we are using Amazon S3 object storage, our EC2 instances must have read, write, and list permissions for our S3 buckets. To avoid embedding AWS keys in our GitLab configuration, we make use of an IAM Role to allow our GitLab instance with this access. We must create an IAM policy to attach to our IAM role:

Create an IAM Policy

  1. Go to the IAM dashboard and select Policies in the left menu.

  2. Select Create policy, select the JSON tab, and add a policy. We want to follow security best practices and grant least privilege, giving our role only the permissions needed to perform the required actions.

    1. Assuming you prefix the S3 bucket names with gl- as shown in the diagram, add the following policy:
    {   "Version": "2012-10-17",
        "Statement": [
            {
                "Effect": "Allow",
                "Action": [
                    "s3:PutObject",
                    "s3:GetObject",
                    "s3:DeleteObject",
                    "s3:PutObjectAcl"
                ],
                "Resource": "arn:aws:s3:::gl-*/*"
            },
            {
                "Effect": "Allow",
                "Action": [
                    "s3:ListBucket",
                    "s3:AbortMultipartUpload",
                    "s3:ListMultipartUploadParts",
                    "s3:ListBucketMultipartUploads"
                ],
                "Resource": "arn:aws:s3:::gl-*"
            }
        ]
    }
  3. Select Next to review the policy. Give your policy a name (we use gl-s3-policy), and select Create policy.

Create an IAM Role

  1. Still on the IAM dashboard, select Roles in the left menu, and select Create role.
  2. For the Trusted entity type, select AWS service. For the Use case, select EC2 for both the dropdown list and radio buttons and select Next.
  3. In the policy filter, search for the gl-s3-policy we previously created, select it, and select Next.
  4. Give the role a name (we use GitLabS3Access). If required, add some tags. Select Create role.

We use this role when we create a launch template later on.

GitLab supports AWS Instance Metadata Service Version 2 (IMDSv2). GitLab automatically uses IMDSv2 when available and falls back to IMDSv1 if needed. You can safely require IMDSv2 on your EC2 instances for enhanced security.

Configuring the network

We start by creating a VPC for our GitLab cloud infrastructure, then we can create subnets to have public and private instances in at least two Availability Zones (AZs). Public subnets require a Route Table keep and an associated Internet Gateway.

Creating the Virtual Private Cloud (VPC)

We now create a VPC, a virtual networking environment that you control:

  1. Sign in to Amazon Web Services.

  2. Select Your VPCs from the left menu and then select Create VPC. At the “Name tag” enter gitlab-vpc and at the “IPv4 CIDR block” enter 10.0.0.0/16. If you don’t require dedicated hardware, you can leave “Tenancy” as default. Select Create VPC when ready.

    Create a VPC for the GitLab cloud infrastructure.

  3. Select the VPC, select Actions, select Edit VPC Settings and check Enable DNS resolution. Select Save when done.

Subnets

Now, let’s create some subnets in different Availability Zones. Make sure that each subnet is associated to the VPC we just created and that CIDR blocks don’t overlap. This also allows us to enable multi AZ for redundancy.

We create private and public subnets to match load balancers and RDS instances as well:

  1. Select Subnets from the left menu.

  2. Select Create subnet. Give it a descriptive name tag based on the IP, for example gitlab-public-10.0.0.0, select the VPC we created previously, select an availability zone (we use us-west-2a), and at the IPv4 CIDR block let’s give it a 24 subnet 10.0.0.0/24:

    Create a subnet.

  3. Follow the same steps to create all subnets:

    Name tagTypeAvailability ZoneCIDR block
    gitlab-public-10.0.0.0publicus-west-2a10.0.0.0/24
    gitlab-private-10.0.1.0privateus-west-2a10.0.1.0/24
    gitlab-public-10.0.2.0publicus-west-2b10.0.2.0/24
    gitlab-private-10.0.3.0privateus-west-2b10.0.3.0/24
  4. Once all the subnets are created, enable Auto-assign IPv4 for the two public subnets:

    1. Select each public subnet in turn, select Actions, and select Edit subnet settings. Check the Enable auto-assign public IPv4 address option and save.

Internet Gateway

Now, still on the same dashboard, go to Internet Gateways and create a new one:

  1. Select Internet Gateways from the left menu.

  2. Select Create internet gateway, give it the name gitlab-gateway and select Create.

  3. Select it from the table, and then under the Actions dropdown list choose “Attach to VPC”.

    Create an internet gateway.

  4. Choose gitlab-vpc from the list and hit Attach.

Create NAT Gateways

Instances deployed in our private subnets must connect to the internet for updates, but should not be reachable from the public internet. To achieve this, we make use of NAT Gateways deployed in each of our public subnets:

  1. Go to the VPC dashboard and select NAT Gateways in the left menu bar.
  2. Select Create NAT Gateway and complete the following:
    1. Availability mode: Select Zonal.
    2. Subnet: Select gitlab-public-10.0.0.0 from the dropdown list.
    3. Elastic IP Allocation ID: Enter an existing Elastic IP or select Allocate Elastic IP address to allocate a new IP to your NAT gateway.
    4. Add tags if needed.
    5. Select Create NAT Gateway.

Create a second NAT gateway but this time place it in the second public subnet, gitlab-public-10.0.2.0.

Route Tables

Public Route Table

We must create a route table for our public subnets to reach the internet via the internet gateway we created in the previous step.

On the VPC dashboard:

  1. Select Route Tables from the left menu.
  2. Select Create Route Table.
  3. At the “Name tag” enter gitlab-public and choose gitlab-vpc under “VPC”.
  4. Select Create.

We now must add our internet gateway as a new target and have it receive traffic from any destination.

  1. Select Route Tables from the left menu and select the gitlab-public route to show the options at the bottom.
  2. Select the Routes tab, select Edit routes > Add route and set 0.0.0.0/0 as the destination. In the target column, select the Internet Gateway and select the gitlab-gateway we created previously. Select Save changes when done.

Next, we must associate the public subnets to the route table:

  1. Select the Subnet Associations tab and select Edit subnet associations.
  2. Check only the public subnets and select Save associations.

Private Route Tables

We also must create two private route tables so that instances in each private subnet can reach the internet via the NAT gateway in the corresponding public subnet in the same availability zone.

  1. Follow the previous steps to create two private route tables. Name them gitlab-private-a and gitlab-private-b.
  2. Next, add a new route to each of the private route tables where the destination is 0.0.0.0/0 and the target is one of the NAT gateways we created earlier.
    1. Add the NAT gateway we created in gitlab-public-10.0.0.0 as the target for the new route in the gitlab-private-a route table.
    2. Similarly, add the NAT gateway in gitlab-public-10.0.2.0 as the target for the new route in the gitlab-private-b.
  3. Lastly, associate each private subnet with a private route table.
    1. Associate gitlab-private-10.0.1.0 with gitlab-private-a.
    2. Associate gitlab-private-10.0.3.0 with gitlab-private-b.

Load Balancer

We create a load balancer to evenly distribute inbound traffic across our GitLab application servers. Based on the scaling policies we create later, instances are added to or removed from our load balancer as needed. Additionally, the load balancer performs health checks on our instances.

AWS offers two approaches for this architecture:

  • Network Load Balancer (NLB) only: A simpler setup suitable for smaller deployments. The NLB handles all traffic (SSH on port 22, HTTP on port 80, and HTTPS on port 443) directly to the Rails nodes, with SSL/TLS termination at the NLB.
  • Hybrid NLB->ALB approach: A more scalable setup that separates concerns. The NLB handles TCP traffic (SSH on port 22), while an Application Load Balancer (ALB) handles HTTP/HTTPS traffic with SSL/TLS termination. This approach enables AWS WAF integration and better traffic management.

Choose the approach that best fits your deployment.

graph TB
    Users["Users"]
    NLB["Network Load Balancer<br/>(Port 22, 80, 443)"]
    Rails1["Rails Node 1<br/>(Port 22, 80)"]
    Rails2["Rails Node 2<br/>(Port 22, 80)"]
    
    Users -->|SSH| NLB
    Users -->|HTTP| NLB
    Users -->|HTTPS| NLB
    NLB -->|Port 22| Rails1
    NLB -->|Port 22| Rails2
    NLB -->|Port 80, 443| Rails1
    NLB -->|Port 80, 443| Rails2

This section describes the simpler NLB-only approach where a single Network Load Balancer handles all traffic types, routing SSH, HTTP, and HTTPS directly to Rails nodes.

We need a security group for this architecture:

  1. NLB Security Group (gitlab-nlb-sec-group):
    • Inbound: TCP port 22 from anywhere (or restrict to trusted IP ranges for SSH)
    • Inbound: TCP port 80 from anywhere
    • Inbound: TCP port 443 from anywhere
    • Outbound: All traffic

To create this security group:

  1. From the EC2 dashboard, select Security Groups from the left menu bar.
  2. Select Create security group.
  3. Give it a descriptive name and description, and select the gitlab-vpc from the VPC dropdown list.
  4. Add the inbound rules as specified above.
  5. When done, select Create security group.

Create the target groups:

  1. On the EC2 dashboard, select Target Groups from the left menu bar.

  2. Select Create target group for the SSH Target Group:

    SettingValue
    Target typeInstances
    Target group namegitlab-nlb-ssh-target
    ProtocolTCP
    Port22
    VPCgitlab-vpc
    Health check protocolTCP

    Select Next twice, then Create target group. You will register targets later.

  3. Select Create target group again for the HTTP Target Group:

    SettingValue
    Target typeInstances
    Target group namegitlab-nlb-http-target
    ProtocolTCP
    Port80
    VPCgitlab-vpc
    Health check protocolHTTP
    Health check path/-/readiness

    Select Next, choose Register Later, then Next twice and Create target group.

Create the network load balancer:

  1. On the EC2 dashboard, look for Load Balancers in the left navigation bar and select Create Load Balancer.

  2. Choose Network Load Balancer and select Create.

  3. Configure the load balancer with the following settings:

    SettingValue
    Load Balancer namegitlab-nlb
    SchemeInternet-facing
    IP address typeIPv4
    VPCgitlab-vpc
    MappingSelect both public subnets
    Security groupgitlab-nlb-sec-group
  4. In the Listeners and routing section, configure:

    ProtocolPortTarget group
    TCP22gitlab-nlb-ssh-target
    TCP80gitlab-nlb-http-target
    TLS443gitlab-nlb-http-target

    For the TLS listener on port 443, under Security Policy settings:

  5. Select Create load balancer.

Targets for the gitlab-nlb-ssh-target and gitlab-nlb-http-target target groups are automatically registered when instances launch in the auto scaling group created later in this guide.

graph TB
    Users["Users"]
    NLB["Network Load Balancer<br/>(Port 22, 443)"]
    ALB["Application Load Balancer<br/>(Port 443)"]
    Rails1["Rails Node 1<br/>(Port 22, 80)"]
    Rails2["Rails Node 2<br/>(Port 22, 80)"]
    
    Users -->|SSH| NLB
    Users -->|HTTPS| NLB
    NLB -->|Port 22| Rails1
    NLB -->|Port 22| Rails2
    NLB -->|Port 443| ALB
    ALB -->|Port 80| Rails1
    ALB -->|Port 80| Rails2

This section describes a hybrid approach where a Network Load Balancer handles SSH traffic and an Application Load Balancer handles HTTP/HTTPS traffic. The NLB routes TCP port 22 (SSH) directly to Rails nodes and TCP port 443 (HTTPS) to the ALB, and the ALB terminates SSL/TLS and routes HTTP traffic to Rails nodes on port 80. This approach enables AWS WAF integration and better separation of concerns.

We need three security groups for this architecture:

  1. NLB Security Group (gitlab-nlb-sec-group):

    • Inbound: TCP port 22 from anywhere (or restrict to trusted IP ranges for SSH)
    • Inbound: TCP port 443 from anywhere (or restrict to trusted IP ranges for HTTPS)
    • Outbound: TCP port 22 to gitlab-rails-sec-group
    • Outbound: TCP port 443 to gitlab-alb-sec-group
  2. ALB Security Group (gitlab-alb-sec-group):

    • Inbound: TCP port 443 from gitlab-nlb-sec-group
    • Inbound: TCP port 80 from gitlab-rails-sec-group
    • Outbound: TCP port 80 to gitlab-rails-sec-group
  3. Rails Security Group (gitlab-rails-sec-group):

    • Inbound: TCP port 22 from gitlab-nlb-sec-group
    • Inbound: TCP port 80 from gitlab-alb-sec-group

To create these security groups:

  1. From the EC2 dashboard, select Security Groups from the left menu bar.
  2. Select Create security group for the SSH Target Group:
  3. Give each a descriptive name and description, and select the gitlab-vpc from the VPC dropdown list.
  4. Add the inbound rules as specified above. When selecting a source, choose Security group and select the appropriate security group from the dropdown.
  5. When done, select Create security group.

Create the target groups:

  1. On the EC2 dashboard, select Target Groups from the left menu bar.

  2. Create the NLB SSH Target Group with the following settings:

    SettingValue
    Target typeInstances
    Target group namegitlab-nlb-ssh-target
    ProtocolTCP
    Port22
    VPCgitlab-vpc
    Health check protocolTCP

    Select Next twice, then Create target group. You will register targets later.

  3. Select Create target group again for the NLB to ALB Target Group:

    SettingValue
    Target typeApplication Load Balancer
    Target group namegitlab-nlb-alb-target
    ProtocolTCP
    Port443
    VPCgitlab-vpc
    Health check protocolHTTPS
    Health check path/-/readiness

    Select Next, choose Register Later for the Application Load Balancer, then Next and Create target group.

  4. Select Create target group again for the ALB HTTP Target Group:

    SettingValue
    Target typeInstance
    Target group namegitlab-alb-http-target
    ProtocolHTTP
    Port80
    VPCgitlab-vpc
    Protocol versionHTTP1.1
    Health check protocolHTTP
    Health check path/-/readiness

    Select Next, choose Register Later, then Next twice and Create target group.

Create the application load balancer:

  1. On the EC2 dashboard, look for Load Balancers in the left navigation bar and select Create Load Balancer.

  2. Choose Application Load Balancer and select Create.

  3. Configure the load balancer with the following settings:

    SettingValue
    Load Balancer namegitlab-alb
    SchemeInternet-facing
    IP address typeIPv4
    VPCgitlab-vpc
    MappingSelect both public subnets gitlab-public-10.0.0.0 and gitlab-public-10.0.2.0
    Security groupgitlab-alb-sec-group
  4. In the Listeners and routing section, configure:

    ProtocolPortActionTarget group
    HTTPS443Forward togitlab-alb-http-target

    For the HTTPS listener, select your ACM certificate and choose an appropriate security policy (see Predefined SSL Security Policies for Application Load Balancers).

  5. Select Create load balancer.

Create the network load balancer:

  1. On the EC2 dashboard, look for Load Balancers in the left navigation bar and select Create Load Balancer.

  2. Choose Network Load Balancer and select Create.

  3. Configure the load balancer with the following settings:

    SettingValue
    Load Balancer namegitlab-nlb
    SchemeInternet-facing
    IP address typeIPv4
    VPCgitlab-vpc
    MappingSelect both public subnets gitlab-public-10.0.0.0 and gitlab-public-10.0.2.0
    Security groupgitlab-nlb-sec-group
  4. In the Listeners and routing section, configure:

    ProtocolPortTarget group
    TCP22gitlab-nlb-ssh-target
    TCP443gitlab-nlb-alb-target
  5. Select Create load balancer.

Register the ALB as a target for the NLB:

  1. On the EC2 dashboard, select Target Groups from the left menu bar.
  2. Select the gitlab-nlb-alb-target target group.
  3. On the Targets tab, select Register targets.
  4. Select the gitlab-alb Application Load Balancer and select Register pending targets.
  5. Select Save.

Targets for the gitlab-nlb-ssh-target and gitlab-alb-http-target target groups are automatically registered when instances launch in the auto scaling group created later in this guide.

After the NLB load balancer is up and running, you can revisit your Security Groups to refine the access only through the NLB and any other requirements you might have.

Some attributes can only be configured after the load balancer has been created. Here are a couple of features you might configure based on your requirements:

  • Client IP preservation is enabled for the target groups by default. This allows the IP of the client connected in the Load Balancer to be preserved in the GitLab application. You can enable/disable this based on your requirements.
  • Proxy Protocol is disabled for the target groups by default. This allows the Load Balancer to send additional information in the proxy protocol headers. If you want to enable this, make sure that other environment components like internal load balancers, NGINX, etc. are configured as well. For this POC we only need to enable it in the GitLab node later.

Configure DNS for Load Balancer

On the Route 53 dashboard, select Hosted zones in the left navigation bar:

  1. Select an existing hosted zone or, if you do not already have one for your domain, select Create Hosted Zone, enter your domain name, and select Create.
  2. Select Create record and provide the following values:
    1. Name: Use the domain name (the default value) or enter a subdomain.
    2. Type: Select A - IPv4 address.
    3. Alias: Defaults to disabled. Enable this option.
    4. Route traffic to: Select Alias to Network Load Balancer.
    5. Region: Select the region where the Network Load Balancer resides.
    6. Choose network load balancer: Select the Network Load Balancer we created earlier.
    7. Routing Policy: We use Simple but you can choose a different policy based on your use case.
    8. Evaluate Target Health: We set this to No but you can choose to have the load balancer route traffic based on target health.
    9. Select Create.
  3. If you registered your domain through Route 53, you’re done. If you used a different domain registrar, you must update your DNS records with your domain registrar. You must:
    1. Select Hosted zones and select the domain you added previously.
    2. You see a list of NS records. From your domain registrar’s administrator panel, add each of these as NS records to your domain’s DNS records. These steps may vary between domain registrars. If you’re stuck, Google “name of your registrar” add DNS records and you should find a help article specific to your domain registrar.

The steps for doing this vary depending on which registrar you use and is beyond the scope of this guide.

PostgreSQL with RDS

For our database server we use Amazon RDS for PostgreSQL which offers Multi AZ for redundancy (Aurora is not supported). First we create a security group and subnet group, then we create the actual RDS instance.

RDS Security Group

We need a security group for our database that allows inbound traffic from the instances we deploy in our gitlab-nlb-sec-group later on:

  1. From the EC2 dashboard, select Security Groups from the left menu bar.
  2. Select Create security group.
  3. Give it a name (we use gitlab-rds-sec-group), a description, and select the gitlab-vpc from the VPC dropdown list.
  4. In the Inbound rules section, select Add rule and set the following:
    1. Type: search for and select the PostgreSQL rule.
    2. Source type: set as “Custom”.
    3. Source: select the appropriate security group based on your load balancer approach:
      • NLB only: gitlab-nlb-sec-group
      • Hybrid NLB->ALB: gitlab-rails-sec-group
  5. When done, select Create security group.

RDS Subnet Group

  1. Go to the RDS dashboard and select Subnet Groups from the left menu.
  2. Select Create DB Subnet Group.
  3. Under Subnet group details, enter a name (we use gitlab-rds-group), a description, and choose the gitlab-vpc from the VPC dropdown list.
  4. From the Availability Zones dropdown list, select the Availability Zones that include the subnets you’ve configured. In our case, we add us-west-2a and us-west-2b.
  5. From the Subnets dropdown list, select the two private subnets (10.0.1.0/24 and 10.0.3.0/24) as we defined them in the subnets section.
  6. Select Create when ready.

Create the database

Avoid using burstable instances (t class instances) for the database as this could lead to performance issues due to CPU credits running out during sustained periods of high load.

Now, it’s time to create the database:

  1. Go to the RDS dashboard, select Databases from the left menu, and select Create database.

  2. Select Standard Create for the database creation method.

  3. Select PostgreSQL as the database engine and select the minimum PostgreSQL version as defined for your GitLab version in our database requirements.

  4. Because this is a production server, let’s choose Production from the Templates section.

  5. Under Availability & durability, select Multi-AZ DB instance to have a standby RDS instance provisioned in a different Availability Zone.

  6. Under Settings, use:

    • gitlab-db-ha for the DB instance identifier.
    • gitlab for a master username.
    • A very secure password for the master password.

    Make a note of these as we need them later.

  7. For the DB instance size, select Standard classes and select an instance size that meets your requirements from the dropdown list. We use a db.m5.large instance.

  8. Under Storage, configure the following:

    1. Select Provisioned IOPS (SSD) from the storage type dropdown list. Provisioned IOPS (SSD) storage is best suited for this use (though you can choose General Purpose (SSD) to reduce the costs). Read more about it at Storage for Amazon RDS.
    2. Allocate storage and set provisioned IOPS. We use the minimum values, 100 and 1000.
    3. Enable storage autoscaling (optional) and set a maximum storage threshold.
  9. Under Connectivity, configure the following:

    1. Under the Virtual Private Cloud (VPC) dropdown list select the VPC we created earlier (gitlab-vpc).
    2. Under the DB subnet group select the subnet group (gitlab-rds-group) we created earlier.
    3. Set public access to No.
    4. Under VPC security group, select Choose existing and select the gitlab-rds-sec-group we created previously from the dropdown list.
    5. Under Additional configuration leave the database port as the default 5432.
  10. For Database authentication, select Password authentication.

  11. Expand the Additional configuration section and complete the following:

    1. The initial database name. We use gitlabhq_production.
    2. Configure your preferred backup settings.
    3. The only other change we make here is to disable auto minor version updates under Maintenance.
    4. Leave all the other settings as is or tweak according to your needs.
    5. If you’re happy, select Create database.

Now that the database is created, let’s move on to setting up Redis with ElastiCache.

Redis with ElastiCache

ElastiCache is an in-memory hosted caching solution. Redis maintains its own persistence and is used to store session data, temporary cache information, and background job queues for the GitLab application.

Create a Redis Security Group

  1. Go to the EC2 dashboard.
  2. Select Security Groups from the left menu.
  3. Select Create security group and fill in the details. Give it a name (we use gitlab-redis-sec-group), add a description, and choose the VPC we created earlier (gitlab-vpc).
  4. In the Inbound rules section, select Add rule and add a Custom TCP rule, set port 6379, and set the “Custom” source based on your load balancer approach:
    • NLB only: gitlab-nlb-sec-group
    • Hybrid NLB->ALB: gitlab-rails-sec-group
  5. When done, select Create security group.

Redis Subnet Group

  1. Go to the ElastiCache dashboard from your AWS console.

  2. Go to Subnet Groups in the left menu, and create a new subnet group (we name ours gitlab-redis-group). Select the VPC we created earlier (gitlab-vpc) and ensure the selected subnets table only contains the private subnets.

  3. Select Create when ready.

    Create a subnet group for the GitLab Redis group.

Create the Redis Cluster

  1. Go back to the ElastiCache dashboard.

  2. Select Redis caches on the left menu and select Create Redis cache to create a new Redis cluster.

  3. Under Deployment option select Design your own cache.

  4. Under Creation method select Cluster cache.

  5. Under Cluster mode select Disabled as it is not supported. Even without cluster mode on, you still get the chance to deploy Redis in multiple availability zones.

  6. Under Cluster info give the cluster a name (gitlab-redis) and a description.

  7. Under Location select AWS Cloud and enable Multi-AZ option.

  8. In the Cluster settings section:

    1. For the Engine version, select the Redis version as defined for your GitLab version in our Redis requirements.
    2. Leave the port as 6379 because this is what we previously used in our Redis security group.
    3. Select the node type (at least cache.t3.medium, but adjust to your needs) and the number of replicas.
  9. In the Connectivity settings section:

    1. Network type: IPv4
    2. Subnet groups: Select Choose existing subnet group and choose the gitlab-redis-group we had previously created.
  10. In the Availability Zone placements section:

    1. Manually select the preferred availability zones, and under “Replica 2” choose a different zone than the other two.

      Choose the availability zones for the Redis group.

  11. Select Next.

  12. In the security settings, edit the security groups and choose the gitlab-redis-sec-group we had previously created. Select Next.

  13. Leave the rest of the settings to their default values or edit to your liking.

  14. When done, select Create.

Setting up Bastion Hosts

Because our GitLab instances are in private subnets, we need a way to connect to these instances with SSH for actions that include making configuration changes and performing upgrades. One way of doing this is by using a bastion host, sometimes also referred to as a jump box.

If you do not want to maintain bastion hosts, you can set up AWS Systems Manager Session Manager for access to instances. This is beyond the scope of this document.

Create Bastion Host A

  1. Go to the EC2 Dashboard and select Launch instance.
  2. In the Name and tags section, set the Name to Bastion Host A.
  3. Select the latest Ubuntu Server LTS (HVM) AMI. Check the GitLab documentation for the latest supported OS version.
  4. Choose an instance type. We use a t2.micro as we only use the bastion host to SSH into our other instances.
  5. In the Key pair section, select Create new key pair.
    1. Give the key pair a name (we use bastion-host-a) and save the bastion-host-a.pem file for later use.
  6. Edit the Network settings section:
    1. Under VPC, select the gitlab-vpc from the dropdown list.
    2. Under Subnet, select the public subnet we created earlier (gitlab-public-10.0.0.0).
    3. Check that under Auto-assign Public IP you have Disabled selected. An Elastic IP address is assigned later to the host in the next section.
    4. Under Firewall select Create security group, enter a Security group name (we use bastion-sec-group), and add a description.
    5. We enable SSH access from anywhere (0.0.0.0/0). If you want stricter security, specify a single IP address or an IP address range in CIDR notation.
  7. For storage, we leave everything as default and only add an 8 GB root volume. We do not store anything on this instance.
  8. Review all your settings and, if you’re happy, select Launch Instance.

Assign Elastic IP to the Bastion Host A

  1. Go to the EC2 Dashboard and select Network & Security.
  2. Select Elastic IPs and set the Network border group to us-west-2.
  3. Select Allocate.
  4. Select the Elastic IP address that was created.
  5. Select Actions and choose Associate Elastic IP address.
  6. Under the Resource Type select Instance and choose the Bastion Host A host under the Instance dropdown list.
  7. Select Associate.

Confirm that you can SSH into the instance

  1. On the EC2 Dashboard, select Instances in the left menu.
  2. Select Bastion Host A from your list of instances.
  3. Select Connect and follow the connection instructions.
  4. If you are able to connect successfully, let’s move on to setting up our second bastion host for redundancy.

Create Bastion Host B

  1. Create an EC2 instance following the same steps used previously with the following changes:
    1. For the Subnet, select the second public subnet we created earlier (gitlab-public-10.0.2.0).
    2. Under the Add Tags section, we set Key: Name and Value: Bastion Host B so that we can identify our two instances.
    3. For the security group, select the existing bastion-sec-group we previously created.

Use SSH Agent Forwarding

EC2 instances running Linux use private key files for SSH authentication. You connect to your bastion host using an SSH client and the private key file stored on your client. Because the private key file is not present on the bastion host, you are not able to connect to your instances in private subnets.

Storing private key files on your bastion host is a bad idea. To get around this, use SSH agent forwarding on your client.

For example, the command-line ssh client uses agent forwarding with its -A switch, like this:

ssh -A user@<bastion-public-IP-address>

See Securely Connect to Linux Instances Running in a Private Amazon VPC for a step-by-step guide on how to use SSH agent forwarding for other clients.

Install GitLab and create custom AMI

We need a preconfigured, custom GitLab AMI to use in our launch configuration later. As a starting point, we use the official GitLab AMI to create a GitLab instance. Then, we add our custom configuration for PostgreSQL, Redis, and Gitaly. If you prefer, instead of using the official GitLab AMI, you can also spin up an EC2 instance of your choosing and manually install GitLab.

Install GitLab

From the EC2 dashboard:

  1. Use the following section titled Find official GitLab-created AMI IDs on AWS to find the correct AMI and select Launch.
  2. In the Name and tags section, set the Name to GitLab.
  3. In the Instance type dropdown list, select an instance type based on your workload. Consult the hardware requirements to choose one that fits your needs (at least c5.2xlarge, which is sufficient to accommodate 100 users).
  4. In the Key pair section, select Create new key pair.
    1. Give the key pair a name (we use gitlab) and save the gitlab.pem file for later use.
  5. In the Network settings section:
    1. VPC: Select gitlab-vpc, the VPC we created earlier.

    2. Subnet: Select gitlab-private-10.0.1.0 from the list of subnets we created earlier.

    3. Auto-assign Public IP: Select Disable.

    4. Firewall: Choose Select existing security group and select the appropriate security group based on your load balancer approach:

      • NLB only: gitlab-nlb-sec-group and bastion-sec-group
      • Hybrid NLB->ALB: gitlab-rails-sec-group and bastion-sec-group

      The bastion-sec-group allows SSH access from the bastion hosts for management and configuration tasks using SSH Agent Forwarding.

  6. For storage, the root volume is 8 GiB by default and should be enough given that we do not store any data there.
  7. Review all your settings and, if you’re happy, select Launch Instance.

Add custom configuration

Connect to your GitLab instance via Bastion Host A using SSH Agent Forwarding. Once connected, add the following custom configuration:

Disable Let’s Encrypt

Because we’re adding our SSL certificate at the load balancer, we do not need the GitLab built-in support for Let’s Encrypt. Let’s Encrypt is enabled by default when using an https domain, so we must explicitly disable it:

  1. Open /etc/gitlab/gitlab.rb and disable it:

    letsencrypt['enable'] = false
  2. Save the file and reconfigure for the changes to take effect:

    sudo gitlab-ctl reconfigure

Install the required extensions for PostgreSQL

If the gitlab user has the rds_superuser role, GitLab can install the required extensions automatically. In that case, the manual steps below are not needed.

From your GitLab instance, connect to the RDS instance to verify access and to install the required PostgreSQL extensions.

To find the host or endpoint, go to Amazon RDS > Databases and select the database you created earlier. Look for the endpoint under the Connectivity & security tab.

For -h, use only the RDS endpoint hostname - omit the trailing colon and port number:

sudo /opt/gitlab/embedded/bin/psql -U gitlab -h <rds-endpoint> -d gitlabhq_production

Then install each required extension using CREATE EXTENSION:

CREATE EXTENSION IF NOT EXISTS btree_gist;
CREATE EXTENSION IF NOT EXISTS ...;

Verify the installed extensions with \dx.

Configure GitLab to connect to PostgreSQL and Redis

  1. Edit /etc/gitlab/gitlab.rb, find the external_url 'http://<domain>' option and change it to the https domain you are using.

  2. Look for the GitLab database settings and uncomment as necessary. In our current case we specify the database adapter, encoding, host, name, username, and password:

    # Disable the built-in Postgres
     postgresql['enable'] = false
    
    # Fill in the connection details
    gitlab_rails['db_adapter'] = "postgresql"
    gitlab_rails['db_encoding'] = "unicode"
    gitlab_rails['db_database'] = "gitlabhq_production"
    gitlab_rails['db_username'] = "gitlab"
    gitlab_rails['db_password'] = "mypassword"
    gitlab_rails['db_host'] = "<rds-endpoint>"
  3. Next, we must configure the Redis section by adding the host and uncommenting the port:

    # Disable the built-in Redis
    redis['enable'] = false
    
    # Fill in the connection details
    gitlab_rails['redis_host'] = "<redis-endpoint>"
    gitlab_rails['redis_port'] = 6379
    
    # Adjust based on your Redis setting
    gitlab_rails['redis_ssl'] = true
  4. Finally, reconfigure GitLab for the changes to take effect:

    sudo gitlab-ctl reconfigure
  5. You can also run a check and a service status to make sure everything has been setup correctly:

    sudo gitlab-rake gitlab:check
    sudo gitlab-ctl status

Set up Gitaly

In this architecture, having a single Gitaly server creates a single point of failure. Use Gitaly Cluster (Praefect) to remove this limitation.

Gitaly is a service that provides high-level RPC access to Git repositories. It should be enabled and configured on a separate EC2 instance in one of the private subnets we configured previously.

Let’s create an EC2 instance where we install Gitaly:

  1. From the EC2 dashboard, select Launch instance.
  2. In the Name and tags section, set the Name to Gitaly.
  3. Choose an AMI. In this example, we select the latest Ubuntu Server LTS (HVM), SSD Volume Type. Check the GitLab documentation for the latest supported OS version.
  4. Choose an instance type. We pick a m5.xlarge.
  5. In the Key pair section, select Create new key pair.
    1. Give the key pair a name (we use gitaly) and save the gitaly.pem file for later use.
  6. In the Network settings section:
    1. Under VPC, select the gitlab-vpc from the dropdown list.
    2. Under Subnet, select the private subnet we created earlier (gitlab-private-10.0.1.0).
    3. Check that under Auto-assign Public IP you have Disable selected.
    4. Under Firewall select Create security group, enter a Security group name (we use gitlab-gitaly-sec-group), and add a description.
      1. Create a Custom TCP rule and add port 8075 to the Port Range. For the Source, select the appropriate security group based on your load balancer approach:
        • NLB only: gitlab-nlb-sec-group
        • Hybrid NLB->ALB: gitlab-rails-sec-group
      2. Also add an inbound rule for SSH from the bastion-sec-group so that we can connect using SSH Agent Forwarding from the Bastion hosts.
  7. Increase the Root volume size to 20 GiB and change the Volume Type to Provisioned IOPS SSD (io1). (The volume size is an arbitrary value. Create a volume big enough for your repository storage requirements.)
    1. For IOPS set 1000 (20 GiB x 50 IOPS). You can provision up to 50 IOPS per GiB. If you select a larger volume, increase the IOPS accordingly. Workloads where many small files are written in a serialized manner, like git, requires performant storage, hence the choice of Provisioned IOPS SSD (io1).
  8. Review all your settings and, if you’re happy, select Launch Instance.

Instead of storing configuration and repository data on the root volume, you can also choose to add an additional EBS volume for repository storage. Follow the same guidance mentioned previously. See the Amazon EBS pricing page.

Now that we have our EC2 instance ready, follow the documentation to install GitLab and set up Gitaly on its own server. Perform the client setup steps from that document on the GitLab instance we created previously.

Elastic File System (EFS)

We do not recommend using EFS because it can negatively impact the performance of GitLab. For more information, see the documentation about avoiding cloud-based file systems.

If you do decide to use EFS, ensure that the PosixUser attribute is either omitted or correctly specified with the UID and GID of the git user on the system that Gitaly is installed. The UID and GID can be retrieved with the following commands:

# UID
$ id -u git

# GID
$ id -g git

Additionally, you should not configure multiple access points, especially if they specify different credentials. An application other than Gitaly can manipulate permissions on the Gitaly storage directories in a way that prevents Gitaly from operating correctly. For an example of this problem, see omnibus-gitlab issue 8893.

Add Support for Proxied SSL

As we are terminating SSL at our load balancer, follow the steps at Supporting proxied SSL to configure this in /etc/gitlab/gitlab.rb.

Remember to run sudo gitlab-ctl reconfigure after saving the changes to the gitlab.rb file.

Fast lookup of authorized SSH keys

The public SSH keys for users allowed to access GitLab are stored in /var/opt/gitlab/.ssh/authorized_keys. Typically we’d use shared storage so that all the instances are able to access this file when a user performs a Git action over SSH. Because we do not have shared storage in our setup, we update our configuration to authorize SSH users via indexed lookup in the GitLab database.

Follow the instructions at Set up fast SSH key lookup to switch from using the authorized_keys file to the database.

If you do not configure fast lookup, Git actions over SSH results in the following error:

Permission denied (publickey).
fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

Configure host keys

Ordinarily we would manually copy the contents (primary and public keys) of /etc/ssh/ on the primary application server to /etc/ssh on all secondary servers. This prevents false man-in-the-middle-attack alerts when accessing servers in your cluster behind a load balancer.

We automate this by creating static host keys as part of our custom AMI. As these host keys are also rotated every time an EC2 instance boots up, “hard coding” them into our custom AMI serves as a workaround.

On your GitLab instance run the following:

sudo mkdir /etc/ssh_static
sudo cp -R /etc/ssh/* /etc/ssh_static

In /etc/ssh/sshd_config update the following:

# HostKeys for protocol version 2
HostKey /etc/ssh_static/ssh_host_rsa_key
HostKey /etc/ssh_static/ssh_host_dsa_key
HostKey /etc/ssh_static/ssh_host_ecdsa_key
HostKey /etc/ssh_static/ssh_host_ed25519_key

Amazon S3 object storage

Because we’re not using NFS for shared storage, we use Amazon S3 buckets to store backups, artifacts, LFS objects, uploads, merge request diffs, container registry images, and more. Our documentation includes instructions on how to configure object storage for each of these data types, and other information about using object storage with GitLab.

Because we are using the AWS IAM profile we created earlier, be sure to omit the AWS access key and secret access key/value pairs when configuring object storage. Instead, use 'use_iam_profile' => true in your configuration as shown in the object storage documentation linked previously.

When using IAM roles for S3 access, GitLab supports both IMDSv1 and IMDSv2 and automatically uses IMDSv2 when available.

Remember to run sudo gitlab-ctl reconfigure after saving the changes to the gitlab.rb file.


That concludes the configuration changes for our GitLab instance. Next, we create a custom AMI based on this instance to use for our launch configuration and auto scaling group.

IP allowlist

We must add the VPC IP Address Range (CIDR) of the gitlab-vpc we created earlier to the IP allowlist for the Health check endpoints

  1. Edit /etc/gitlab/gitlab.rb:

    gitlab_rails['monitoring_whitelist'] = ['127.0.0.0/8', '10.0.0.0/16']
  2. Reconfigure GitLab:

    sudo gitlab-ctl reconfigure

Proxy Protocol

If Proxy protocol is enabled in the load balancer we created earlier, we must also enable this on the gitlab.rb file.

  1. Edit /etc/gitlab/gitlab.rb:

    nginx['proxy_protocol'] = true
    nginx['real_ip_trusted_addresses'] = [ "127.0.0.0/8", "IP_OF_THE_PROXY/32"]
  2. Reconfigure GitLab:

    sudo gitlab-ctl reconfigure

Sign in for the first time

Using the domain name you used when setting up DNS for the load balancer, you should now be able to visit GitLab in your browser.

Depending on how you installed GitLab and if you did not change the password by any other means, the default password is either:

  • Your instance ID if you used the official GitLab AMI.
  • A randomly generated password stored for 24 hours in /etc/gitlab/initial_root_password.

To change the default password, sign in as the root user with the default password and change it in the user profile.

When our auto scaling group spins up new instances, we are able to sign in with username root and the newly created password.

Create custom AMI

On the EC2 dashboard:

  1. Select the GitLab instance we created earlier.
  2. Select Actions, scroll down to Image and templates and select Create image.
  3. Give your image a name and description (we use GitLab-Source for both).
  4. Leave everything else as default and select Create Image

Now we have a custom AMI that we use to create our launch configuration the next step.

Deploy GitLab inside an auto scaling group

Create a launch template

From the EC2 dashboard:

  1. Select Launch Templates from the left menu and select create launch template.

  2. Enter a name for your launch template (we use gitlab-launch-template).

  3. Select Launch template contents and select My AMIs tab/

  4. Select Owned by me and select the GitLab-Source custom AMI we created previously.

  5. Select an instance type best suited for your needs (at least a c5.2xlarge).

  6. In the Key pair section, select Create new key pair.

    1. Give the key pair a name (we use gitlab-launch-template) and save the gitlab-launch-template.pem file for later use.
  7. The root volume is 8 GiB by default and should be enough given that we do not store any data there. Select Configure Security Group.

  8. Check Select existing security group and select the appropriate security group based on your load balancer approach:

    • NLB only: gitlab-nlb-sec-group and bastion-sec-group
    • Hybrid NLB->ALB: gitlab-rails-sec-group and bastion-sec-group

    The bastion-sec-group allows SSH access from the bastion hosts for management and configuration tasks using SSH Agent Forwarding.

  9. In the Advanced details section:

    1. IAM instance profile: Select the GitLabS3Access role we created earlier.
  10. Review all your settings and, if you’re happy, select Create launch template.

Create an auto scaling group

From the EC2 dashboard:

  1. Select Auto scaling groups from the left menu and select Create Auto Scaling group.

  2. Enter a Group name (we use gitlab-auto-scaling-group).

  3. Under Launch template, select the launch template we created earlier. Select Next

  4. In the Network settings section:

    1. Under VPC, select the gitlab-vpc from the dropdown list.
    2. Under Availability Zones and subnets, select the private subnets we created earlier (gitlab-private-10.0.1.0 and gitlab-private-10.0.3.0).
    3. Select Next.
  5. In the Load Balancing settings section:

    1. Select Attach to an existing load balancer.
    2. In the Existing load balancer target groups dropdown list, select the appropriate target groups based on your load balancer approach:
      • NLB only: Select gitlab-nlb-ssh-target and gitlab-nlb-http-target
      • Hybrid NLB->ALB: Select gitlab-nlb-ssh-target and gitlab-alb-http-target The auto scaling group will automatically register all launched instances to these target groups.
    3. For Health Check Type, check the Turn on Elastic Load Balancing health checks option. We leave our Health Check Grace Period as the default 300 seconds.
    4. Select Next.
  6. For Group size, set Desired capacity to 2.

  7. In the Scaling settings section:

    1. Select No scaling policies. The policies are configured later on.
    2. Min desired capacity: Set to 2.
    3. Max desired capacity: Set to 4.
    4. Select Next.
  8. Finally, configure notifications and tags as you see fit, review your changes, and create the auto scaling group.

  9. After the auto scaling group is created, we need to create a scale up and down policy in Cloudwatch and assign them.

    1. Create an alarm for the CPUUtilization for metrics from EC2 instances By Auto Scaling Group we created earlier.
    2. Create a scale up policy using the following conditions:
      1. Add 1 capacity unit when CPUUtilization is greater than or equal to 60%.
      2. Set the Scaling policy name to Scale Up Policy.

    Configure a Scale Up Policy.

    1. Create a scale down policy using the following conditions:
      1. Remove 1 capacity unit when CPUUtilization is less than or equal to 45%.
      2. Set the Scaling policy name to Scale Down Policy.

    Configure a Scale Down Policy.

    1. Assign the new dynamic scaling policy to the auto scaling group we created earlier.

As the auto scaling group is created, you see your new instances spinning up in your EC2 dashboard. You also see the new instances added to your load balancer. After the instances pass the heath check, they are ready to start receiving traffic from the load balancer.

Because our instances are created by the auto scaling group, go back to your instances and terminate the instance we previously created manually. We only needed this instance to create our custom AMI.

Health check and monitoring with Prometheus

Apart from Amazon CloudWatch, which you can enable on various services, GitLab provides its own integrated monitoring solution based on Prometheus. For more information about how to set it up, see GitLab Prometheus.

GitLab also has various health check endpoints that you can ping and get reports.

GitLab Runner

If you want to take advantage of GitLab CI/CD, you have to set up at least one runner.

Read more on configuring an autoscaling GitLab Runner on AWS.

Backup and restore

GitLab provides a tool to back up and restore its Git data, database, attachments, LFS objects, and so on.

Some important things to know:

Backing up GitLab

To back up GitLab:

  1. SSH into your instance.

  2. Take a backup:

    sudo gitlab-backup create

Restoring GitLab from a backup

To restore GitLab, first review the restore documentation, and primarily the restore prerequisites. Then, follow the steps under the Linux package installations section.

Updating GitLab

GitLab releases a new version every month on the release date. Whenever a new version is released, you can update your GitLab instance:

  1. SSH into your instance

  2. Take a backup:

    sudo gitlab-backup create
  3. Update the repositories and install GitLab:

    sudo apt update
    sudo apt install gitlab-ee

After a few minutes, the new version should be up and running.

Find official GitLab-created AMI IDs on AWS

Read more on how to use GitLab releases as AMIs.

Conclusion

In this guide, we went mostly through scaling and some redundancy options, your mileage may vary.

Keep in mind that all solutions come with a trade-off between cost/complexity and uptime. The more uptime you want, the more complex the solution. And the more complex the solution, the more work is involved in setting up and maintaining it.

Have a read through these other resources and feel free to open an issue to request additional material:

  • Scaling GitLab: GitLab supports several different types of clustering.
  • Geo replication: Geo is the solution for widely distributed development teams.
  • Linux package - Everything you must know about administering your GitLab instance.
  • Add a license: Activate all GitLab Enterprise Edition functionality with a license.
  • Pricing: Pricing for the different tiers.

Troubleshooting

Instances are failing health checks

If your instances are failing the load balancer’s health checks, verify that they are returning a status 200 from the health check endpoint we configured earlier. Any other status, including redirects like status 302, causes the health check to fail.

You may have to set a password on the root user to prevent automatic redirects on the sign-in endpoint before health checks pass.

Message: The change you requested was rejected (422)

If you see this page when trying to set a password via the web interface, make sure external_url in gitlab.rb matches the domain you are making a request from, and run sudo gitlab-ctl reconfigure after making any changes to it.

Some job logs are not uploaded to object storage

When the GitLab deployment is scaled up to more than one node, some job logs may not be uploaded to object storage properly. Incremental logging is required for CI to use object storage.

Enable incremental logging if it has not already been enabled.