# Deploy on Railway (https://portunus.bybee.dev/en/docs/deployment/railway)



Railway can host `portunus-server` as a single service: the Web UI and
operator HTTP API use Railway's public HTTP domain, while the gRPC control
plane for `portunus-client` uses a Railway TCP Proxy pointed at application
port `7443`.

## Template shape [#template-shape]

The Railway build uses [`deploy/railway/Dockerfile`](https://github.com/ZingerLittleBee/Portunus/blob/main/deploy/railway/Dockerfile).
It builds the React Web UI first, embeds `webui/dist/` into
`portunus-server`, then runs the server from a small Debian runtime image.

At startup, [`deploy/railway/start-server.sh`](https://github.com/ZingerLittleBee/Portunus/blob/main/deploy/railway/start-server.sh)
writes a minimal `/var/lib/portunus/server.toml`:

```toml
control_listen = "0.0.0.0:7443"
operator_http_listen = "0.0.0.0:${PORT}"
metrics_listen = "127.0.0.1:7081"
# Appended only when a public origin is resolved (see below):
operator_http_public_origin = "https://${RAILWAY_PUBLIC_DOMAIN}"
```

`$PORT` is the HTTP port assigned by Railway. The control-plane listener stays
on `7443` so Railway's TCP Proxy can expose it separately. The first three
lines are always written; the `operator_http_public_origin` line is only
appended when a public origin is resolved — taken from
`PORTUNUS_OPERATOR_HTTP_PUBLIC_ORIGIN` if set, otherwise from Railway's
`RAILWAY_PUBLIC_DOMAIN`. If neither is set, the line is omitted entirely. Set
`PORTUNUS_OPERATOR_HTTP_PUBLIC_ORIGIN` when you attach a custom domain.

## Required Railway resources [#required-railway-resources]

Configure these in the Railway project:

| Resource               | Value                                                            |
| ---------------------- | ---------------------------------------------------------------- |
| Service build          | Dockerfile, path `deploy/railway/Dockerfile`                     |
| Volume mount           | `/var/lib/portunus`                                              |
| Public HTTP networking | enabled, normal Railway generated domain is fine                 |
| TCP Proxy              | application port `7443`                                          |
| Service variable       | `PORTUNUS_ADVERTISED_ENDPOINT=<tcp-proxy-host>:<tcp-proxy-port>` |

The volume is not optional. It stores `state.db`, generated TLS material, and
the server config. Without it, redeploys create a fresh server identity and
lose operator state.

Railway mounts volumes as `root`; the Railway image intentionally runs as
`root` so the mounted `/var/lib/portunus` path is writable without an extra
service variable.

Set `PORTUNUS_ADVERTISED_ENDPOINT` after creating the TCP Proxy. Railway shows
the generated proxy domain and proxy port in the TCP Proxy settings. Use that
exact `host:port` value; enrollment commands created before this variable is
set will advertise the wrong endpoint and should be recreated.

## First-run onboarding [#first-run-onboarding]

Deploy the service, then open the Railway HTTP domain. The Web UI is served at
`/`.

If the store is empty, the server logs a setup token:

```sh
Portunus onboarding setup token: <token>
```

Paste that token into the Web UI and create the first superadmin account. The
token expires after 30 minutes and rotates on restart while onboarding is still
incomplete.

## Client endpoint [#client-endpoint]

After creating a TCP Proxy for application port `7443`, set
`PORTUNUS_ADVERTISED_ENDPOINT` to the proxy `host:port` and redeploy.
Then enroll clients from the Web UI **Clients** page; the enrollment
command makes `portunus-client` dial the TCP Proxy endpoint. Install and
run the edge client with Docker (see
[Client Configuration](/en/docs/configuration/client#docker)) or the
host installer
([`install.sh`](/en/docs/configuration/client#install-the-binary)).

## Notes [#notes]

* Railway's HTTP domain serves only the Web UI and `/v1/*` operator API.
* `portunus-client` must dial the TCP Proxy endpoint, not the HTTP
  domain.
* The Prometheus metrics listener stays on `127.0.0.1:7081`; Railway does
  not expose it.
* Set `PORTUNUS_CONTROL_LISTEN` only if you change the TCP Proxy
  application port; the default is `0.0.0.0:7443`.
* For a custom domain, set
  `PORTUNUS_OPERATOR_HTTP_PUBLIC_ORIGIN=https://your.custom.domain`.

> On Railway the gRPC host differs from the local bind; set the
> advertised endpoint during install (the interactive wizard prompts for
> it) or later via the installer's `Config` action — see
> [Installation → Interactive manager](/en/docs/getting-started/installation).
