It’s a Plan
Self-hosting

Deploy on Railway

Railway runs the published images and generates every secret itself. You supply two hostnames from a domain you own. The template wires the rest: Postgres, a storage bucket, and the four services of a full It's a Plan instance.

Verified template · AGPL-3.0 · No build step

What the template starts

The template creates six resources in one Railway project:

  • PostgresThe database, on a Railway volume. The api applies its migrations when it starts.
  • BucketRailway object storage, for issue attachments.
  • apiThe HTTP API and the auth handler, on port 3000.
  • webThe web app people sign in to, on port 3001.
  • workerWebhook deliveries, notifications, schedules and agent runs.
  • botThe Telegram bot. It idles until you give it a token.

Railway builds nothing. Each service pulls its image from ghcr.io/croffasia/itsaplan-<service> at the latest tag, which is the newest published release. Railway bills for what these resources use; the rates are on Railway pricing.

Before you start

Two things before the deploy:

  • A Railway accountThe template deploys into a project of your own. Nothing here is run by this project.
  • A domain you controlYou need two hostnames under one registrable domain, and the right to add DNS records for them.

The domain is not optional. Railway generates *.up.railway.app hostnames, and those are on the Public Suffix List: a browser then reads each service as a separate site and refuses the session cookie. Sign-in returns 200 and the login page comes straight back.

Two hostnames under one registrable domain solve this, because the cookie is issued on the parent they share. This page uses these two as the example:

  • api.example.comthe api
  • app.example.comthe web app

Deploy

  1. 1Open the template

    Railway asks for a project name and a region, then shows the six resources.

  2. 2Fill in the two fields on the web service

    API_URL is https://api.example.com, and APP_URL is https://app.example.com. Give the full origin, with https:// and no trailing slash. You set each value once: the api reads both by reference, and web reads API_URL at startup and hands it to the browser on every render.

  3. 3Deploy

    Railway generates the secrets, creates the database and the bucket, and starts the services. The api runs the migrations on its first start.

  4. 4Attach your hostnames

    Until you do, the services answer on Railway hostnames, and sign-in does not work on those. The steps are in Attach the hostnames.

Environment variables

Two variables are yours. Set them on the web service, at deploy time:

  • API_URLPublic origin of the api. Example: https://api.example.com.
  • APP_URLPublic origin of the web app. Example: https://app.example.com.

The template sets the rest. You do not enter these, and you do not keep them:

  • DATABASE_URLA reference to the Postgres service. POSTGRES_PASSWORD is generated per deploy.
  • BETTER_AUTH_SECRETSigns the sessions. Generated per deploy.
  • APP_ENCRYPTION_KEYEncrypts the provider keys stored in the database. Generated per deploy.
  • S3_*Endpoint, bucket and credentials of the Railway bucket. S3_FORCE_PATH_STYLE is false here, because the bucket serves virtual-host style URLs. The RustFS stack in the Compose file needs the default instead.

No secret is stored in the template, so every deploy has its own. Mail, AI provider keys, single sign-on and the Telegram bot token are not environment variables: you configure them in the interface after you sign in.

.env.example lists every variable the product reads, including the optional ones.

Attach the hostnames

After the deploy, open each service and add its hostname under Settings → Networking → Custom Domain:

  • apiapi.example.com, port 3000
  • webapp.example.com, port 3001

Here Railway wants the bare hostname, with no scheme. This is different from the variables above, which carry https://.

Railway then shows a CNAME record and a TXT record for each hostname. Add them at your DNS provider. Behind Cloudflare, keep both records unproxied (DNS only), or the certificate is never issued.

First sign-in

Open APP_URL and register. The first account becomes the instance admin, and admin settings open from there: mail provider, AI providers, single sign-on, the Telegram bot token, and who may register.

Changing a hostname later

Edit API_URL or APP_URL on the web service. Both services restart and serve the new address, and nothing is rebuilt. Then attach the new hostnames as above and remove the old ones.

Updates

Every service ships with auto updates on, in the 02:00 to 06:00 UTC window. Railway watches the latest tag in GHCR and redeploys a service once a release pushes a new image. The api applies the new migrations when it starts, so an upgrade needs no step of yours.

To switch it off for a service, or to narrow the window, open Settings → Source → Configure Auto Updates.

Troubleshooting

Sign-in returns to the login page.

The two hostnames are not under one registrable domain, or they are still the generated *.up.railway.app ones. The browser drops the session cookie. Move both services to your own domain and correct API_URL and APP_URL.

The custom domain stays on 'Waiting for DNS'.

The CNAME record or the TXT record is missing at your DNS provider, or the record is proxied. Behind Cloudflare, set both to DNS only.

The web app loads, but every request to the api fails.

API_URL does not match the hostname attached to the api service. It has to be the full origin, with https:// and no trailing slash. Web reads it at startup, so it takes effect when the service restarts.

Attachments do not upload.

The S3_* variables on the api no longer match the bucket. Restore the values the template set, with S3_FORCE_PATH_STYLE on false.

The bot service runs, but the Telegram bot answers nothing.

The token is a setting, not a variable. Add it in the app, under the admin settings. The bot re-reads its configuration from the database and starts on its own.

For anything else, read the service logs first: open the service, then Deployments → View logs.

Support

Other ways to run it: Docker Compose, Coolify, Kubernetes.