How to deploy your own instance of bors-ng with AWS

A note about resource requirements

Bors-NG is a chat bot, not a full CI system, so you can probably provision it on the smallest VM you have the ability to use. The public instance of Bors-NG, which is only usable for public repositories, has over twenty thousand repositories connected to it, processes between one and four requests per second, and runs in a Heroku hobby tier dyno for $7 a month.

First step: setting up the GitHub App

To set up a GitHub App, your first step is to register it within GitHub. You'll need to copy a lot of data from here to the docker-compose.yml later.

Go to the Settings area on GitHub, then click New GitHub App. You'll need a domain name to act as the ENDPOINT.

Name Set it to
GitHub App Name Set this to something unique and descriptive.
Homepage URL This can technically be set to anything.
Callback URL https://ENDPOINT/auth/github/callback
Expire authorization tokens false
Request User authorization during installation false
Enable Device Flow false
Setup URL (optional) Leave this blank
Redirect on update false
Webhook active true
Webhook URL https://ENDPOINT/webhook/github
Webhook secret Set this to something random. Save it.
Permission Setting
Actions No access
Administration No access
Checks Read & Write
Code scanning alerts No access
Codespaces No access
Codespaces lifecycle admin No access
Codespaces metadata No access
Codespaces secrets No access
Commit statuses Read & Write
Contents Read & Write
Dependabot alerts No access
Dependabot secrets No access
Deployments No access
Discussions No access
Environments No access
Issues Read & Write
Merge queues No access
Packages No access
Pages No access
Projects No access
Pull requests Read & Write
Secret scanning alerts No access
Secrets No access
Single file No access
Webhooks No access
Workflows No access
Organization permisssion Setting
Administration No access
Blocking users No access
Events No access
Members Read-only
Organization codespaces No access
Organization codespaces secrets No access
Organization dependabot secrets No access
Plan No access
Projects No access
Secrets No access
Self-hosted runners No access
Team discussions No access
Webhooks No access
User permission Setting
Block another user No access
Codespaces user secrets No access
Email addresses No access
Followers No access
GPG keys No access
Gists No access
Git SSH keys No access
Interaction limits No access
Plan No access
Profile No access
Starring No access
Watching No access
Subscribe to event Setting
Meta false
Security advisory false
Check run true
Check suite true
Commit comment false
Create false
Delete false
Fork false
Gollum false
Issue comment true
Issues false
Label false
Milestone false
Member true
Membership true
Merge queue entry false
Organization true
Page build false
Public false
Pull request true
Pull request review true
Pull request review comment true
Pull request review thread false
Push false
Release false
Repository true
Repository dispatch false
Star false
Status true
Team true
Team add false
Watch false
Workflow dispatch false
Workflow job false
Workflow run false

Your next step will be generating a private key. Generate it, save it, then run this command to turn it into a base64 string:

$ base64 -w0 < my-bors.2022-08-01.private-key.pem

Also generate and save the client secret.

This leaves us with, in total, three GitHub-specific secret values to keep track of:

  • Webhook secret: used by bors to validate webhooks coming from GitHub. This is the GitHub -> Bors authentication path.
  • Private key: used by bors to authenticate as a bot with GitHub. This is the Bors -> GitHub authentication path.
  • Client secret: used for logging into the bors-ng dashboard with your GitHub account. This is the User -> GitHub authentication path.

Second step: setting up your ENDPOINT domain name in AWS Cert Manager

You'll need to create this so that webhooks and the dashboard work correctly, and you'll also need to remember the ARN you get at the end.

To do this, go into the cert manager, request a public certificate with your ENDPOINT domain name, note the ARN for later, and wait until it's validated.

Third step: running bors-ng in AWS ECS

This sample file is in the bors-ng repository. Clone it, then let's modify it:

$ git clone
$ cd bors-ng/deploy/examples/docker-compose/bors
$ vim docker-compose.yml

Inside that file are several placeholders that need replaced with other values. The bolded parts below are what you need to change:

version: "2"
    image: borsng/bors-ng
    restart: always
      - target: 8000
        x-aws-protocol: http
      # You shouldn't have to change these
      PORT: 8000
      DATABASE_USE_SSL: "false"
      # Replace this with the hostname of your bors instance
      # For example, the public instance is
      # and cockroachdb runs
      PUBLIC_HOST: [registered]
      # Replace this with a random number
      # If you have a bors source checkout, you can run `mix phx.gen.secret` to get one
      SECRET_KEY_BASE: [random]
      # Get these from the GitHub App setup screen under "OAuth credentials"
      GITHUB_CLIENT_ID: [client id]
      GITHUB_CLIENT_SECRET: [client secret]
      # Get this from the GitHub App setup screen under "About"
      # Get this by taking the private key and base64-encoding it
      # For example, `openssl base64 -A -e < bors-merge-queue.2018-08-10.private-key.pem`
      # the result will be long, and will end in one or two equal signs
      GITHUB_INTEGRATION_PEM: [base64 value]
      # Replace this with a random number
      # You will also provide this same number to GitHub when you set up the app
      # Make sure the [censored] password here matches the POSTGRES_PASSWORD
      DATABASE_URL: postgres://postgres:[random]@postgres:5432/bors
      - postgres
    image: postgres:10.5
    restart: always
      - datadb:/var/lib/postgresql/data
      # Make sure the [censored] password here matches the one in the DATABASE_URL
      POSTGRES_PASSWORD: [random]
        HealthCheckPath: /health
          HttpCode: 200-499
          - CertificateArn: [from aws cert manager]
        Protocol: HTTPS
        Port: 443
    driver: bridge

Once you've subbed in all your censored secrets, set up bors-ng itself by running these commands in the same directory as the docker-compose.yml file:

$ docker context create ecs myecscontext
$ docker context use myecscontext
$ docker compose up

The name of the directory does matter, because that's the default name of the docker-compose app.

This process will create a load balancer for you, but will not set up DNS for it. You'll need to go into the AWS EC2 console, get the DNS name for the load balancer (it'll look something like, and set up a CNAME connecting it to the public DNS name.

Once that's set up, you should be able to visit https://ENDPOINT from your browser, and configure the bot from there.

Customizing Bors-NG

If you want to make any changes to the bot, which many enterprise deployments do, you'll need to maintain your own soft-fork. To make this work, you'll be making your own image, and changing the image: borsng/bors-ng line in your docker-compose.yml to point at it instead.

Building a container image with GitHub Actions

After making your local GitHub fork of bors-ng/bors-ng (if you don't mind it being public, click the "Fork" button, otherwise you'll need to create a private repository and push a copy of the code to it), add a new workflow file to automatically build the container.

# .github/workflows/docker.yml
# The reason why bors-ng doesn't already have a file like this is that it uses the Docker Hub,
# not GitHub's container registry.
name: bors-docker
    - master

  contents: write
  packages: write

      - uses: actions/checkout@master
      - uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
          username: ${{ }}
          password: ${{ secrets.GITHUB_TOKEN }}
      # replace [owner] with an actual org or user
      - run: docker build -t[owner]/bors-ng .
      - run: docker push[owner]/bors-ng

Also, patch the docker-compose.yml file, and then run docker compose up again:

version: "2"
    restart: always
      - target: 8000
        x-aws-protocol: http
# ----------- most of the file is unchanged -----------