- TypeScript 99.1%
- CSS 0.4%
- Shell 0.2%
- Dockerfile 0.1%
- Makefile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| .github | ||
| packages | ||
| scripts | ||
| .dockerignore | ||
| .gitignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| entrypoint.sh | ||
| kiosk.example.yaml | ||
| KIOSK.md | ||
| LICENSE | ||
| Makefile | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| THIRD_PARTY.md | ||
| tsconfig.base.json | ||
Gatwy
Self-host your entire remote access stack in one Docker container — 10 protocols, one interface: RDP, SSH, VNC, Telnet, SMB, SFTP, FTP, MySQL & PostgreSQL (Optional - Moonlight).
No middleware. No Java. No server relay. WebAssembly-native RDP running directly in your browser — zero overhead across all protocols.
🚀 Why Gatwy?
Most browser-based remote access tools relay your display through a server-side engine, adding latency and complexity. Gatwy's RDP client runs entirely in your browser using WebAssembly — pixel-perfect, low-latency RDP with no middleware, no Java, and no extra containers.
One container. Zero dependencies. Open your browser and connect.
✨ Highlights
| Feature | What you get | |
|---|---|---|
| 10 protocols | RDP (WebAssembly), SSH, VNC, Telnet, SMB, SFTP, FTP, PostgreSQL, MySQL — plus optional Moonlight | |
| Split-pane workspace | Unlimited sessions side by side with drag-and-drop tabs | |
| Session recording & audit | Encrypted RDP video, SSH asciinema, command-level audit log with auto-redacted passwords, file activity tracking | |
| Credential library | Reusable username/password and SSH key credentials; rotate a password once and every connection follows | |
| Granular RBAC | 30 fine-grained permissions, custom roles, per-connection sharing, protocol-level access control | |
| Auth flexibility | Local accounts, LDAP/AD, OpenID Connect (SSO), MFA (TOTP), IP access rules | |
| Alerting | SMTP, Telegram, Slack, Webhook channels with a no-code rule builder | |
| Encrypted backup & restore | Single-file .geb backup with AES-256 encryption |
🐳 Quick Start
Docker Compose
# docker-compose.yml
services:
gatwy:
image: ghcr.io/kotoxie/gatwy:latest
container_name: gatwy
restart: unless-stopped
ports:
- '7443:7443'
volumes:
- ./data:/app/data
environment:
- GATWY_ENCRYPTION_KEY=your-64-char-hex-key # openssl rand -hex 32
docker compose up -d
Docker Run
docker run -d \
--name gatwy \
--restart unless-stopped \
-p 7443:7443 \
-v ./data:/app/data \
-e GATWY_ENCRYPTION_KEY=your-64-char-hex-key \
ghcr.io/kotoxie/gatwy:latest
Then open https://<YOUR_IP>:7443 and create your admin account on first launch.
Note
The browser will warn about the self-signed certificate. Accept the exception to proceed, or bring your own cert.
⚙️ Configuration
Core
| Variable | Default | Description |
|---|---|---|
GATWY_ENCRYPTION_KEY |
(auto-generated file) | 64-char hex AES-256 key. ⚠️ Set this in production. Generate with openssl rand -hex 32 |
PORT |
7443 |
HTTPS port |
DATA_DIR |
/app/data |
Database, certs, recordings, and logs |
TLS_CERT_PATH / TLS_KEY_PATH |
(auto) | Custom TLS certificate & key paths |
BASE_PATH |
/ (root) |
URL path prefix for serving behind a reverse proxy (e.g. /sys/ftp). Plain path segments only (A-Z a-z 0-9 . _ ~ -); leading/trailing slashes are normalized. |
Authentication
| Variable | Default | Description |
|---|---|---|
OIDC_TOKEN_AUTH_METHOD |
client_secret_basic |
OIDC token endpoint client auth: client_secret_basic (or basic) or client_secret_post (or post). |
GATWY_KIOSK_CONFIG |
<DATA_DIR>/kiosk.yaml |
Path to the kiosk RDP config file (see below). |
Note: If no encryption key is set, Gatwy auto-generates one at
/app/data/encryption.keyand shows a warning banner. Fine for a home lab — not recommended for production.
Kiosk RDP (?username= param)
With data/kiosk.yaml in place (see kiosk.example.yaml), any logged-in user can open
https://<gatwy-host>:7443/?username=alice
and the entire UI is skipped: the app connects RDP-only to the single host from the YAML config, using alice as both the remote username and the password (password = username), sized to fit the window — nothing on screen but the RDP canvas. Useful for throwaway/virtual users on a fixed LAN host:
enabled: true
rdp:
host: 192.168.1.125
port: 3389
verify_tls: false # skip certificate validation on the target
auth:
user: kiosk # optional: auto-login visitors as this local account (no MFA)
With auth.user set, visitors who are not signed in to Gatwy are signed in automatically — they only ever see the remote desktop, never a login page. Without it, the normal Gatwy login is shown first (the ?username= param survives the redirect), then the kiosk session starts. The account's role still needs the RDP protocol permission.
Warning: password = username is intentionally weak — use only on trusted networks. See
KIOSK.mdfor the full reference (URL params, config keys, auto-login flow, security notes).
Kiosk render params
| Param | Values | Default | Effect |
|---|---|---|---|
res |
WxH (e.g. 1920x1080) |
(window size) | Fixed desktop resolution sent to the host; the window no longer resizes the remote desktop |
scale |
fit / stretch / 1 |
fit |
Display scaling: fit letterboxes preserving aspect ratio, stretch fills the window (may distort), 1 shows native pixels centered (may overflow) |
quality |
10–100 |
100 |
Renders the desktop at N% of the window size (fewer pixels = less bandwidth, upscaled on display). Ignored when res is set |
https://<gatwy-host>:7443/?username=alice&res=1920x1080&scale=fit&quality=75
Warning
password = usernameis inherently weak — the kiosk mode is meant for isolated LAN segments or disposable accounts only.
👉 Full configuration reference →
🌙 Optional: Moonlight / Sunshine
Moonlight is off by default. The helper it uses, moonlight-web-stream, is GPL-3.0 and is not bundled — Gatwy itself stays MIT.
Tip
Enable it by setting
ENABLE_MOONLIGHTto1,true, oryes.
services:
gatwy:
environment:
- ENABLE_MOONLIGHT=1
On start, the entrypoint downloads a pinned release into /opt/moonlight-web and Moonlight appears in the protocol picker. PIN pairing uses the Sunshine web UI.
When the variable is unset, Moonlight is hidden; stored Moonlight connections are kept and return if you enable it later.
See THIRD_PARTY.md for the license note and pinned release.
🔄 Updating
docker compose pull && docker compose up -d
🛠️ Building from Source
git clone https://github.com/kotoxie/gatwy && cd gatwy
# With Docker
docker compose up --build -d
# Without Docker (Node.js 20+)
npm install && npm run build && npm start