Open-source project
icubaby/SideRail avatar
icubaby/SideRail

SideRail: a control panel for a proxy core, deployed on Railway

Bold control panel for your server, one-click deploy on Railway

592 stars1,461 forksTypeScriptNOASSERTION

At a glance

What is it?
SideRail is a TypeScript control panel for an Xray based proxy core, built as a React and Vite front end over an Express and Node backend, with users, inbounds, subscriptions and an activity log. It deploys to Railway in six steps with no environment variables required, and it only exposes port 8080 because every transport it supports runs over HTTP behind a TLS edge.
Who is it for?
Adopt SideRail if you want to run a proxy core for a small group of users on a platform where you cannot manage processes directly, and you want a panel that gives each user a subscription link with a QR code instead of hand editing configuration. Do not adopt it if you need transports that are not HTTP based, because the project supports only HTTP-based transports behind a TLS edge and says so explicitly, and do not expose its internal ports.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A panel for a core engine, not a proxy client

SideRail presents itself as a bold, neobrutalist control panel shipped to Railway in one click, and the repository description calls it a control panel for your server with one-click deploy on Railway. The subject it manages is a proxy core: the badges include Xray-core, and the architecture refers to the core engine throughout, with a core API port, a core version to fetch on first boot, and a configuration builder that disables sniffing.

The relationship is worth being precise about, because it determines what the project is for. SideRail is not a client and not a protocol implementation. It is a server side administration surface for a core engine process: it fetches that engine on first boot, starts it, watches it, seeds its inbounds, creates users against it, and generates subscription links for those users.

The platform constraint shapes everything. The README says it plainly: SideRail masters HTTP-based transports behind a TLS edge, every connection is clean, standard, and always security=tls on port 443. TLS is terminated at the Railway edge, and the Node process routes WebSocket and HTTPUpgrade traffic to the core engine via a raw net.Socket, while XHTTP goes through an HTTP proxy. Only HTTP-based transports are supported, and anything that cannot survive the edge is intentionally left out.

That is a clear product decision with a clear cost, and it is the first question to ask about any deployment of this panel.

Six steps, one exposed port, and a volume

The deployment section is presented as requiring no code at all, and the steps are short enough to quote.

You fork the repository, create a Railway project with New Project and Deploy from GitHub repo, and pick your fork. Railway reads the Dockerfile and railway.json and builds automatically. Then you expose the port: open Settings, then Networking, then Generate Domain, and set the port to 8080. Optionally you attach a Volume at /data so users, admins and settings survive redeploys. Finally you open the generated domain, land on the Setup page, and create your Owner account.

The port rule is the part to internalise, and the README puts it in an IMPORTANT callout. SideRail listens on port 8080, and that is the only port you expose. The ports 10085 and 20000 to 20004 are the core engine's internal ports, bound to 127.0.0.1, and they are private and must not be exposed.

The .env.example file in the repository confirms the shape of that arrangement. The core API port is 10085, described as the internal core API port that you usually do not need to change, and the inbound base port is 20000, the base port for internal inbound listeners. The public port is PORT, 8080 by default, and the data directory is SIDERAIL_DATA_DIR at /data, which is where you mount the volume.

The setup page creating the Owner account is the last step and it is the trust anchor: the first account to be created owns the installation.

The Node backend: core process, tunnels, users and roles

The architecture block in the README shows the server side as a set of single purpose modules, and it is the clearest description of the system.

text
apps/
├─ web/           React + Vite + Tailwind (neobrutalism) frontend
│  └─ src/
│     ├─ components/ui/   Reusable neobrutalist primitives
│     ├─ pages/           Dashboard · Users · Inbounds · Activity · Settings · Setup · Login · Subscription
│     └─ lib/             API client, auth context, helpers
└─ server/        Express + Node (node:sqlite) backend
   └─ src/
      ├─ core.ts          Binary fetch, process manager, traffic stats, client IPs
      ├─ core-config.ts   Config builder (sniffing fully disabled)
      ├─ tunnel.ts        WS/HTTPUpgrade via net.Socket, XHTTP via http-proxy
      ├─ links.ts         Connection link generation
      ├─ inbounds.ts      Default inbound seeding + enable/disable
      ├─ users.ts         User model, traffic reset, sub-token logic
      ├─ auth.ts          Owner/Admin roles, permissions, sessions
      ├─ ratelimit.ts     Rate limiting / brute-force protection
      └─ routes.ts        Authenticated REST API

core.ts is the supervisor. It fetches the binary, manages the process, collects traffic statistics and resolves client IP addresses. core-config.ts builds the core's configuration file, with sniffing fully disabled. tunnel.ts is the transport layer, and it is where the HTTP-only constraint is implemented.

auth.ts and ratelimit.ts are the security surface, and they are interesting because they are the parts a panel like this has to get right. The roles are Owner and Admin, and the README describes the team model as the Owner adding Admins with scoped page access and personal data quotas, where every Admin only ever sees the users they created. That is a real multi-tenant isolation rule rather than a shared admin view.

Storage is node:sqlite, the Node built-in SQLite binding, and the whole backend is Express with Node.

Environment variables that all claim to be optional

The environment table is headed All optional, and that claim is mostly true, with one caveat.

PORT defaults to 8080 and is the one to expose. JWT_SECRET signs the admin session cookies, and is auto-generated and persisted if unset, which is what makes the zero environment variable claim work. CORE_VERSION defaults to v26.9.9, the core engine release fetched on first boot, so the version of the proxy core you run is a decision you can pin or override. SIDERAIL_DATA_DIR defaults to /data, the persistent data directory where you mount a volume. PUBLIC_DOMAIN overrides the domain used to build client links and falls back to RAILWAY_PUBLIC_DOMAIN. CORE_API_PORT is 10085 and CORE_INBOUND_BASE_PORT is 20000, both internal.

The caveat is in the .env.example file, which is more explicit than the README table. The JWT_SECRET line is commented as a secret used to sign admin session cookies with the instruction to change this, and it ships with a placeholder value of change-me-to-a-long-random-string. If you deploy with the value unset and let the application generate one, the secret is auto-generated and persisted, which is the intended path. If you set one by hand, change it.

The same file notes that PUBLIC_DOMAIN can be left empty because Railway sets RAILWAY_PUBLIC_DOMAIN automatically, and that you should override it only for a custom domain.

So the honest summary is: the panel is usable with no configuration, but the two values you most need to reason about are the session secret and the core version.

Users, inbounds, subscriptions and the activity log

The feature table is where the product shape becomes clear.

The Dashboard gives live CPU, RAM, Swap and Storage metrics, plus one-click Backup and Restore of users, inbounds, admins and settings. Users get a rich create form, summary cards, a responsive table, live connected IPs and per-user actions. Inbounds come as five HTTP inbounds auto-seeded on first boot, where you can only enable or disable each one, and each has a unique port and a random /api/... path. The Activity Log is a live timeline of every administrative event, and Settings and Admins cover the Owner and Admin roles, scoped permissions and per-admin data quotas.

The Subscription feature is the one that makes the panel useful to the people it serves. Each user gets a page with a live usage chart, QR codes and a base64 export for clients, which is the difference between handing someone a configuration blob and handing them a link their phone can scan.

The security row is short: JWT sessions, built-in rate limiting, and sniffing fully disabled for stability. The last of those is a deliberate trade, and it is the sort of decision that deserves a sentence of its own, because sniffing is what lets a proxy identify which protocol a client is using, and disabling it in favour of predictability is a choice about reliability over cleverness.

The neobrutalist style is a deliberate constraint too, described as thick borders, hard shadows and animated icons that look sharp on phone and desktop alike.

Local development, releases and the licence question

Running it locally needs Node.js 22.5 or newer, with Node 24 recommended, and the workspace scripts are short:

bash
npm install     # install all workspaces
npm run dev     # web on :5173, server on :8080 (proxied)

npm run build   # production build (web + server)
npm start       # serve API + built frontend on :8080

The repository is an npm workspace with apps/server and apps/web, and the root scripts fan out to them. The development script runs both concurrently, with the web dev server on 5173 proxying to the API on 8080.

The Dockerfile does the same thing in two stages. The build stage starts from node:24-alpine, installs with npm ci using prefer-offline and no audit, builds the workspace, and then prunes the dev dependencies so the runtime stage can reuse the tree. The runtime stage copies node_modules and the built server into the image, copies the built web output into public, sets PORT and SIDERAIL_DATA_DIR and CORE_VERSION, exposes 8080, and starts node apps/server/dist/index.js. The runtime image adds ca-certificates and unzip, which is what the first boot core download needs.

On releases, the recent tags are v3.0.0 on 2026-10-11 and v2.3.0 on 2026-09-29, and the last push to the repository was on 2026-10-21, so the project is actively moving. A major version bump alongside active commits is a signal that the configuration format or the API may have changed between those tags.

On the licence, the root package.json says MIT and the repository has both a LICENSE and a NOTICE file, while the platform metadata classifies the licence as a custom one that it cannot categorise. The LICENSE file is the text to read, particularly if you intend to run a panel that holds user credentials and traffic data for other people.

Editorial conclusion

Adopt SideRail if you want to run a proxy core for a small group of users on a platform where you cannot manage processes directly, and you want a panel that gives each user a subscription link with a QR code instead of hand editing configuration. Do not adopt it if you need transports that are not HTTP based, because the project supports only HTTP-based transports behind a TLS edge and says so explicitly, and do not expose its internal ports. Verify first that your platform can run a long-lived process and a persistent volume at /data, that the core version pinned by CORE_VERSION is one you are willing to run, and that the licence file in the repository matches what the package metadata claims, since the metadata classifies the licence as a custom one while the package.json says MIT.

Frequently asked questions

What is SideRail?

SideRail is a neobrutalist control panel for a server, built to deploy to Railway in one click, that manages a proxy core engine. It is written in TypeScript with a React and Vite front end over an Express and Node backend using node:sqlite.

Which ports do I need to expose for SideRail?

Only port 8080, which is the SideRail HTTP port. The ports 10085 and 20000 to 20004 are the core engine's internal ports, bound to 127.0.0.1, and the README states they are private and must not be exposed.

Does SideRail need environment variables?

The README says all of them are optional. PORT defaults to 8080, JWT_SECRET is auto-generated and persisted if unset, CORE_VERSION defaults to v26.9.9, SIDERAIL_DATA_DIR defaults to /data, and PUBLIC_DOMAIN falls back to RAILWAY_PUBLIC_DOMAIN.

How do I deploy SideRail to Railway?

Fork the repository, create a Railway project with Deploy from GitHub repo, pick your fork, and Railway builds the Dockerfile and railway.json automatically. Then generate a domain under Settings and Networking, set the port to 8080, optionally attach a Volume at /data, and open the generated domain to create your Owner account on the Setup page.

Official sources

  1. icubaby/SideRail on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/icubaby-siderail.svg)](https://hysenlabs.com/projects/icubaby-siderail)

Was this useful?