Skip to content

Run OpenD on Railway

One Railway project runs one OpenD instance: a web service, a worker, Postgres and a bucket.

On Railway the web service and the worker are separate services that cannot share a disk, so blobs go to a Railway Bucket. Railway has deprecated railway.json, so apply the settings below per service, in the dashboard or through the Railway API. See Railway's documentation for the platform itself.

  1. Create a project and add Postgres and a Bucket.
  2. Add two services from this repository, web and worker, both building deploy/Dockerfile, and configure them.
    Settingwebworker
    Sourcethis repository, branch mainsame
    Dockerfile pathdeploy/Dockerfilesame
    Start commandnode dist/bin.js servenode dist/bin.js worker
    Pre-deploynode dist/bin.js migratenone
    Healthcheck/healthz, timeout 120 snone
    Restart policyon failure, 5 retriesalways
    Watch pathspackages/**, deploy/Dockerfile, package.json, pnpm-lock.yamlsame
    Public domainyes, port 3000no

    A start command replaces the image's entry point, so always give the full node dist/bin.js <command>. Watch paths keep docs-only commits from redeploying the app: docs reach readers through the worker, not through a redeploy.

  3. Set the variables on both services, using reference variables for Postgres and the bucket.
    DATABASE_URL=${{Postgres.DATABASE_URL}}
    S3_BUCKET=${{Bucket.BUCKET}}
    S3_ENDPOINT=${{Bucket.ENDPOINT}}
    S3_ACCESS_KEY_ID=${{Bucket.ACCESS_KEY_ID}}
    S3_SECRET_ACCESS_KEY=${{Bucket.SECRET_ACCESS_KEY}}
    S3_REGION=${{Bucket.REGION}}
    PUBLIC_URL=https://<web domain>
    SESSION_SECRET=<openssl rand -hex 32>
    OPEND_ADMIN_TOKEN=<openssl rand -hex 32>
    GITHUB_WEBHOOK_SECRET=<openssl rand -hex 32>
    NODE_ENV=production

    Replace Bucket with your bucket service's name. Railway buckets use virtual-hosted addressing, so leave S3_FORCE_PATH_STYLE unset. Add the OIDC_* variables for reader sign-in, and OPEND_EMBED_SECRET and OPEND_EMBED_ORIGINS to embed help in your apps.

  4. Deploy, and wait for web to report healthy.

    Its pre-deploy step creates the schema. The worker retries until the schema exists.

  5. Register the docs repository, and add organizations and readers, through the admin API.
    curl -X POST "$PUBLIC_URL/admin/api/repos" -H "authorization: Bearer $OPEND_ADMIN_TOKEN" \
      -H 'content-type: application/json' \
      -d '{"fullName":"acme/docs","sourceUrl":"https://github.com/acme/docs.git","releaseRefs":["main","v*"]}'
  6. Publish: point a GitHub webhook at $PUBLIC_URL/webhooks/github, or trigger a build directly.
    curl -X POST "$PUBLIC_URL/admin/api/repos/1/builds" -H "authorization: Bearer $OPEND_ADMIN_TOKEN" \
      -H 'content-type: application/json' -d '{"ref":"main","sha":"<full commit sha>"}'

A public demo can set NODE_ENV=development and OPEND_DEV_LOGIN=1 so visitors pick a sample reader. Never do this with real content.

To add the web editor, create a third service from the same Dockerfile with the start command node dist/bin.js editor serve and its own domain. See Run the web editor.

Tagged releases are published as images to ghcr.io/desktopmachineshop/opend, so a service can deploy an image tag instead of building from the repository.