docker-backstage-devcontainers icon indicating copy to clipboard operation
docker-backstage-devcontainers copied to clipboard

Backstage with Docker and Devcontainers

Dockerized Backstage.io with Devcontainers

This is a PoC repository to run Backstage.io with Docker and Devcontainers.

The main goal here is to demonstrate how to use Development Containers in a real-world scenario but leaving open the door to use a standard dockerized workflow. Another advantage of this approach is performance; Docker on macOS suffers of filesystem performance issues, to fix that:

  1. Code src/backstage is bind mounted to /usr/src/app
  2. OPTIONAL: Node modules are mounted with a named volume on src/backstage/node\_modules

How is implemented

Let's start by commenting devcontainer.json:

{
  "name": "Backstage",

  // When running inside devcontainers we want to use named volumes for node_modules.
  "dockerComposeFile": [
    "../docker-compose.yml",
    "../docker-compose-volumes.yml"
  ],

  // Which docker-compose service to use for the devcontainer instance.
  "service": "cli",

  // https://containers.dev/implementors/json_reference/#variables-in-devcontainerjson
  "workspaceFolder": "${localWorkspaceFolder}",
  "shutdownAction": "stopCompose",
  "userEnvProbe": "loginInteractiveShell",

  // Run it as a non-root user.
  "remoteUser": "node",

  // Just run the cli container, devcontainer does not respect COMPOSE_PROFILES
  "runServices": ["cli"],

  // Add some vscode extensions.
  "customizations": {
    "vscode": {
      "extensions": [
        "dbaeumer.vscode-eslint",
        "esbenp.prettier-vscode",
        "ms-vsliveshare.vsliveshare",
        "eamodio.gitlens"
      ]
    }
  },

  // This is useful to have docker and docker-compose working inside the container
  // Ref: https://github.com/devcontainers/features/tree/main/src/docker-from-docker
  "features": {
    "ghcr.io/devcontainers/features/docker-from-docker:1": {
      "dockerDashComposeVersion": "v2"
    }
  }
}

And now the docker compose files:

# docker-compose.yml

version: "3"
services:
  # This is the development container, can be used
  # with and without devcontainers.
  cli:
    build:
      context: .
      dockerfile: build/node/Dockerfile
      target: cli
    # We use another profile for the cli, to exclude it from running,
    # with a docker-compose up
    profiles: ["cli"]
    env_file:
      - app.env
    volumes:
      # This is used to have the same local path, inside the container.
      - $PWD:$PWD

      # The following volumes are here to keep npm/yarn caching and
      # bash history.
      - ./.cache/yarn:/usr/local/share/.cache/yarn
      - ./.cache/npm:/root/.npm
      - ./.cache/dotfiles:/dotfiles
    # Non root.
    user: node
    hostname: cli

    # Start in the same local root.
    working_dir: $PWD

    # Keep it always running, this is needed by devcontainers.
    command: /bin/sh -c "while sleep 1000; do :; done"

  # This is the container to just run backstage.
  app:
    build:
      context: .
      dockerfile: build/node/Dockerfile
      target: run
    env_file:
      - app.env
    volumes:
      - ./src/backstage/:/usr/src/app
      - ./.cache/yarn:/usr/local/share/.cache/yarn
      - ./.cache/npm:/root/.npm

    # This is the app profile, used to spin up the services with
    # a docker-compose up
    profiles: ["app"]
    ports:
      - 3000:3000
      - 7007:7007
    user: node
    depends_on:
      - db

  db:
    image: postgres:13.3
    volumes:
      - ./database:/usr/src/app
    profiles: ["app"]
    env_file:
      - app.env

---
# docker-compose-volumes.yml

version: "3"
services:
  cli:
    # Adding the named volume on top of the bind mount.
    # NOTE: The volume here must be mounted under the full path of the
    # backstage source code.
    volumes:
      # @TODO - Hardcoded src/backstage path must be a variables.
      - node_modules:$PWD/src/backstage/node_modules
  app:
    # Adding the named volume on top of the bind mount.
    volumes:
      - node_modules:/usr/src/app/node_modules
# Node modules volume.
volumes:
  node_modules:

Using or not the named volume must be controlled with .env variables:

# Uncomment this to use a docker volumes for node_modules.
# IT IS REQUIRED when working with devcontainers.

# COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
COMPOSE_PROFILES=app

Run it

All bind mounted

Just keep .env:

# COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
COMPOSE_PROFILES=app

And run:

> docker-compose up -d
> make cli

Local: Bind mount + named volume

Change .env to:

COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
COMPOSE_PROFILES=app

And run:

> docker-compose up -d
> make cli

Devcontainers: Bind mount + named volume

Change .env to:

COMPOSE_FILE=docker-compose.yml:docker-compose-volumes.yml
COMPOSE_PROFILES=app

Now open the VSCode command palette and run: Dev Containers: Rebuild and Reopen in Containers

At this point you'll have a working instance of VSCode configured with the devcontainers, just open a terminal and run:

> docker-compose up -d

Yes, thanks to the extension installed, we can use docker as we do in the host.