Running the Backstage service catalog with Docker Compose

Last validated on January 9th, 2021 • Originally published on June 9th, 2020

In this tutorial, we’re going to build and run a basic Backstage application with Docker Compose. The application will be able to store data in a PostgreSQL database, and connect to GitHub to pull in repositories. We will also make a config change in the Backstage application and re-run it.


To complete this tutorial, you will need:

  • Docker and Docker Compose installed and running on your local machine.
  • NodeJS installed on your local machine.
  • The Yarn package manager installed. You can use npm if you like, although you will have to modify the shell commands somewhat.

Step 1 - Scaffold a Backstage application

To run Backstage on Docker Compose, we need to create a Backstage instance to work with. The main Backstage codebase does ship with a sample application we can run, but best practices dictate that we should create our own so we can configure it with our company name and other attributes.

Backstage requires a database to store information about the components, websites and other entities you want to track in the catalog. There are two built in database options, Sqlite and PostgreSQL. We’re going to use PostgreSQL for this tutorial.

Backstage comes with a CLI for creating Backstage instances. Let’s use it to scaffold a new instance and configure it for PostgreSQL. We’ll call this instance scaffolded-app, but you can choose a name that makes more sense for you.

This tutorial uses version 0.3.2 of the Backstage CLI to create this application. You may see different results if you’re using a different version.

» npx @backstage/create-app --version

» npx @backstage/create-app
npx: installed 68 in 14.197s
? Enter a name for the app [required] scaffolded-app
? Select database for the backend [required] PostgreSQL

Creating the app...

 Checking if the directory is available:
  checking      scaffolded-app ✔

 Creating a temporary app directory:
  creating      temporary directory ✔

 Preparing files:
  copying ✔
  copying       .npmignore ✔
  copying       lerna.json ✔
  templating    app-config.yaml.hbs ✔
  templating    package.json.hbs ✔
  copying       tsconfig.json ✔
  copying       .eslintrc.js ✔
  copying       cypress.json ✔
  templating    package.json.hbs ✔
  copying       .eslintrc.js ✔
  copying       android-chrome-192x192.png ✔
  copying       favicon-16x16.png ✔
  copying       apple-touch-icon.png ✔
  copying       favicon-32x32.png ✔
  copying       favicon.ico ✔
  copying       manifest.json ✔
  copying       index.html ✔
  copying       safari-pinned-tab.svg ✔
  copying       robots.txt ✔
  copying       App.tsx ✔
  copying       App.test.tsx ✔
  copying       index.tsx ✔
  copying       apis.ts ✔
  copying       plugins.ts ✔
  copying       sidebar.tsx ✔
  copying       setupTests.ts ✔
  copying       .eslintrc.json ✔
  copying       app.js ✔
  copying       .eslintrc.js ✔
  copying       Dockerfile ✔
  copying ✔
  templating    package.json.hbs ✔
  copying       index.ts ✔
  copying       types.ts ✔
  copying       index.test.ts ✔
  copying       auth.ts ✔
  copying       catalog.ts ✔
  copying       identity.ts ✔
  copying       proxy.ts ✔
  copying       scaffolder.ts ✔
  copying       techdocs.ts ✔

 Moving to final location:
  moving        scaffolded-app ✔

 Building the app:
  executing     yarn install ✔
  executing     yarn tsc ✔
  executing     yarn build ✔

🥇  Successfully created scaffolded-app

If we cd into the scaffolded-app directory which was just created, we can see the directory structure which was created for us.

» ls -al                                                                                                                                                                                                                                                                                                                            146 ↵
total 1776
drwxr-xr-x    19 myuser  staff     608  9 Jan 20:20 .
drwxr-xr-x     3 myuser  staff      96  9 Jan 19:17 ..
-rw-r--r--     1 myuser  staff      36  9 Jan 19:17 .eslintrc.js
-rw-r--r--     1 myuser  staff     420  9 Jan 19:17 .gitignore
-rw-r--r--     1 myuser  staff      93  9 Jan 19:17
-rw-r--r--     1 myuser  staff     184  9 Jan 19:17 app-config.production.yaml
-rw-r--r--     1 myuser  staff    3250  9 Jan 19:17 app-config.yaml
-rw-r--r--     1 myuser  staff     399  9 Jan 19:17 catalog-info.yaml
drwxr-xr-x     4 myuser  staff     128  9 Jan 19:19 dist-types
-rw-r--r--     1 myuser  staff     116  9 Jan 19:17 lerna.json
drwxr-xr-x  1698 myuser  staff   54336  9 Jan 19:19 node_modules
-rw-r--r--     1 myuser  staff    1339  9 Jan 19:17 package.json
drwxr-xr-x     4 myuser  staff     128  9 Jan 19:17 packages
-rw-r--r--     1 myuser  staff     272  9 Jan 19:17 tsconfig.json
-rw-r--r--     1 myuser  staff  829904  9 Jan 19:19 yarn.lock

The main bulk of the application is in the packages directory. This contains two subdirectories.

» ls -al packages
total 0
drwxr-xr-x   4 myuser  staff  128  9 Jan 19:17 .
drwxr-xr-x  19 myuser  staff  608  9 Jan 22:23 ..
drwxr-xr-x  10 myuser  staff  320  9 Jan 19:40 app
drwxr-xr-x   9 myuser  staff  288  9 Jan 19:50 backend

The app subdirectory contains the frontend UI of Backstage and the backend, as you might expect, contains the API layer and parts that connect to the database.

Step 2 - Building a Docker image

Backstage comes with a built in command to help you build a Docker image which you can run with Docker Compose.

For simple deployments, the Backstage backend has the ability to serve the frontend app to the browser, so you only have to build one Docker image.

» yarn workspace backend build-image
yarn workspace v1.22.10
yarn run v1.22.10
$ backstage-cli backend:build-image --build --tag backstage
# Lots of output omitted...
=> => naming to                                                                                                                                                                                                                                                                                                                                           0.0s
✨  Done in 114.02s.

Check the image has been built successfully.

» docker images                                                                                                                                                                                                                                                                                                                                                                                                                                       1 ↵
REPOSITORY         TAG       IMAGE ID       CREATED         SIZE
backstage          latest    7b452013e713   3 minutes ago   1.1GB

Now that we have a Docker image, let’s try to run it.

» docker run backstage
2021-01-09T19:51:13.883Z backstage info Loaded config from app-config.yaml, app-config.production.yaml
2021-01-09T19:51:13.887Z backstage info Created UrlReader predicateMux{readers=azure{,authed=false},bitbucket{,authed=false},github{,authed=false},gitlab{,authed=false},fallback=fetch{}}
Backend failed to start up, Error: connect ECONNREFUSED

This fails because the Backstage backend cannot connect to port 5432. Backstage needs to connect to the database in order to store catalog items and other data. It expects to find PostgreSQL running on port 5432. When it can’t, it fails and bails out.

To fix this, let’s use Docker Compose to make PostgreSQL available to our Backstage backend.

Step 2 - Adding PostgreSQL

Below is a simple docker-compose.yaml file which runs the Backstage image we just created and a default PostgreSQL database. Create this file inside your Backstage application and save it.

version: '3'
    image: backstage
      # This value must match the name of the postgres configuration block.
      POSTGRES_USER: postgres
      - '7000:7000'

    image: postgres
    restart: always
	# NOT RECOMMENDED for a production environment. Trusts all incomming
      # connections.

Once you’ve done that, you can use Docker Compose to start both of these Docker images.

» docker-compose up
Creating network "blog-post-test_default" with the default driver
Creating blog-post-test_db_1        ... done
Creating blog-post-test_backstage_1 ... done
Attaching to blog-post-test_backstage_1, blog-post-test_db_1
# Lots of output omitted...
backstage_1  | Backend failed to start up, Error: Failed to initialize github scaffolding provider, Missing required config value at 'scaffolder.github.token'
blog-post-test_backstage_1 exited with code 1

It still fails, but we’ve made progress. Backstage has successfully connected to the database and then failed because of a missing GitHub token.

Step 3 - Configuring GitHub

Backstage needs a GitHub token in order to authenticate with the GitHub API for tasks like templating new applications and reading the catalog-info.yaml files it uses to store metadata.

Head over to the GitHub docs to learn how to create a Personal Access Token. If you don’t want to use GitHub, you can use a nonsense value like abc in place of the GitHub token value.

Once you have your token, pass it into Backstage via the environment variables.

version: '3'
    image: backstage
      POSTGRES_USER: postgres
      # Add your token here
      GITHUB_TOKEN: <your-github-token>
      - '7000:7000'

    image: postgres
    restart: always

Once that’s done, let’s give it one more go.

» docker-compose up
Creating network "blog-post-test_default" with the default driver
Creating blog-post-test_db_1        ... done
Creating blog-post-test_backstage_1 ... done
Attaching to blog-post-test_backstage_1, blog-post-test_db_1
# Lots of output omitted...
backstage_1  | 2021-01-09T22:42:27.061Z backstage info Initializing http server
backstage_1  | 2021-01-09T22:42:27.065Z backstage info Listening on :7000

Hurray! 🎉 Now, if you visit localhost:7000, you should see Backstage.

Backstage running in the browser

Step 4 - Making a change

Our Backstage instance isn’t quite as perfect as it could be. You’ll notice the header says “My Company Service Catalog”. Let’s change that to include the name of our company, Roadie.

This is a simple change to make. Fire up your text editor and open the app-config.yaml file.

In there, you’ll see the following two lines

  name: My Company

Simply change “My Company” to something like “Roadie”, rebuild the docker image, run docker-compose up and refresh your browser window to see the change.


In this tutorial you learned how to get Backstage running locally and change it’s configuration. As a next step, you may wish to try adding the Lighthouse plugin to the deployment.

Become a Backstage expert

To get the latest news, deep dives into Backstage features, and a roundup of recent open-source action, sign up for Roadie's Backstage Weekly. See recent editions.

We will never sell or share your email address.