You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 1d79787
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: README.md
+6-3Lines changed: 6 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -116,7 +116,8 @@ Create a `.env` file in the project root or set these environment variables:
116
116
|`DOCKIFY_DEV_MOCK`|`false`| No | Enable mock SSH for local development (no real VMs needed) |
117
117
|`CLOUDFLARE_API_TOKEN`| — | No | Cloudflare API token for automatic DNS A records |
118
118
|`CLOUDFLARE_ZONE_ID`| — | No | Cloudflare zone ID |
119
-
|`DOCKIFY_BASE_PATH`| — | No | URL prefix when behind a reverse proxy (e.g. `/proxy/9898`) |
119
+
|`DOCKIFY_BASE_PATH`| — | No | URL prefix when behind a reverse proxy (e.g. `/proxy/9898`); links and redirects stay under it. A proxy can send `X-Forwarded-Prefix` per request instead |
120
+
|`DOCKIFY_TRUST_PROXY`|`false`| No |`true` takes the client IP from `X-Forwarded-For`/`X-Real-IP` (login throttle, logs). Set it only when every request comes through a proxy that sets these headers |
120
121
121
122
### Authentication
122
123
@@ -134,6 +135,7 @@ DOCKIFY_ADMIN_PASSWORD=secret123 docker compose up -d
134
135
-**Default username:**`admin` (configurable via `DOCKIFY_ADMIN_USER`)
135
136
-**No password set:** Dockify refuses to start unless it binds to loopback (`DOCKIFY_HOST=127.0.0.1`, `::1` or `localhost`) or `DOCKIFY_ALLOW_NO_AUTH=true` is set; in both cases the web UI has **no authentication**
136
137
-**Password set:** All routes (except `/health` and `/api/webhook/*`) require login
138
+
-**Failed logins:** each one waits 1 s; more than 10 from one client IP within 15 minutes get `429` until the 15 minutes are over. The client IP is the TCP peer unless `DOCKIFY_TRUST_PROXY=true`. Behind a reverse proxy (e.g. Caddy in front of `127.0.0.1:8080`) set `DOCKIFY_TRUST_PROXY=true` and keep the port unreachable from outside; otherwise every client counts as the proxy and failed logins from anyone lock everyone out for 15 minutes
-**Cross-site requests:** a form or script on another site cannot change anything (`403`). Behind a reverse proxy, forward the original `Host` header (Caddy does this by default)
139
141
-**Logout:** Visit `/logout`
@@ -467,7 +469,7 @@ No DNS automation occurs if the env vars are not set.
467
469
468
470
## Backup & Restore
469
471
470
-
Export your configuration (servers + apps + secrets + config files) as YAML and import it into another instance.
472
+
Export your configuration (servers + apps with their resource limits, command, ports and every domain + secrets + config files) as YAML and import it into another instance.
471
473
472
474
### Encrypted Export
473
475
@@ -487,7 +489,7 @@ When importing on a new instance, all servers and their SSH keys are restored au
487
489
Upload a previously exported YAML file via the Import page (`/import`):
488
490
489
491
- **merge** (default): skip entries that already exist by name
490
-
- **replace**: delete all existing servers and apps before import
492
+
- **replace**: delete all existing servers and apps before import; refused unless every app is stopped, a draft or never deployed (stop or undeploy running apps first)
491
493
- Encrypted values are validated by attempting decryption before the actual import
492
494
493
495
### Unified Backup Page
@@ -512,6 +514,7 @@ Dockify's web UI is built with Go `html/template` + HTMX and fully custom CSS (n
512
514
513
515
- **Dark/Light mode** — toggle via nav button, persisted in `localStorage` (`dockify-theme`)
514
516
- **HTMX partial updates** — resource cards, log viewer, and status badges update without full page reloads
517
+
- **No CDN scripts** — htmx and xterm.js are embedded in the binary and served from `/static/`; a Content-Security-Policy limits scripts to Dockify itself (only Google Fonts is loaded from outside)
515
518
- **Responsive** — adapts to mobile screen sizes (single breakpoint at 600px)
516
519
- **Status badges** — color-coded for all server and app states
|`DOCKIFY_ALLOW_NO_AUTH`|`false`| No | Allow a passwordless UI on a non-loopback bind |
107
108
|`DOCKIFY_API_TOKEN`| — | No |`Authorization: Bearer` token accepted on all protected routes (scripts/agents) |
108
109
|`DOCKIFY_DEV_MOCK`|`false`| No | Enable mock SSH client for local development |
109
-
|`DOCKIFY_BASE_PATH`| — | No | URL prefix when behind reverse proxy (e.g. `/proxy/9898`) |
110
+
|`DOCKIFY_BASE_PATH`| — | No | URL prefix when behind reverse proxy (e.g. `/proxy/9898`); `X-Forwarded-Prefix` overrides it per request. Templates use links relative to `<base href>`, redirects go through `web.Redirect`, which prepends the prefix |
111
+
|`DOCKIFY_TRUST_PROXY`|`false`| No | Apply `chimw.RealIP` (client IP from `X-Forwarded-For`/`X-Real-IP`); off = TCP peer address |
110
112
|`CLOUDFLARE_API_TOKEN`| — | No | Cloudflare API token for DNS automation (Zone:DNS:Edit scope) |
111
113
|`CLOUDFLARE_ZONE_ID`| — | No | Cloudflare zone ID for the domain |
112
114
@@ -254,7 +256,7 @@ Written by the background collectors, pruned after 7 days, `created_at` in UTC.
254
256
| POST |`/logout`| Clear session, redirect to login |
255
257
| POST |`/api/webhook/github`| GitHub push webhook (HMAC-SHA256), body ≤ 1 MiB |
256
258
| POST |`/api/webhook/gitlab`| GitLab push webhook (token), body ≤ 1 MiB |
257
-
| GET |`/static/*`|Static files (`web/static/`)|
259
+
| GET |`/static/*`|Vendored htmx/xterm assets embedded in the binary (`internal/http/static/`, sources in `SOURCES.md`); one-week cache, no directory listing|
258
260
259
261
Protected routes accept the session cookie or `Authorization: Bearer $DOCKIFY_API_TOKEN`. A POST/PATCH/DELETE sent by another site (`Sec-Fetch-Site`, or `Origin` ≠ `Host` for older browsers) gets `403`; bearer-token requests are exempt. WebSocket handshakes must be same-origin. Request bodies under `/api/servers` and `/api/apps` are capped at 1 MiB.
260
262
@@ -313,7 +315,7 @@ Protected routes accept the session cookie or `Authorization: Bearer $DOCKIFY_AP
2. Query all apps (name, domain, port, compose, git_repo, git_branch, auth_user, auth_pass, compose_mode, server name mapping)
426
+
2. Query all apps (name, domain, port, compose, git_repo, git_branch, auth_user, auth_pass, compose_mode, memory_limit, cpu_limit, log_max_size, log_max_file, command, ports, ulimits_nofile, server name mapping) and `domains`: every active route's domain, the primary first
425
427
3. Query all app secrets and config files
426
428
4. If passphrase provided: encrypt SSH keys, registry tokens, secrets, auth_pass, and file contents with AES-GCM (PBKDF2 key derivation, 600,000 iterations, `enc:` prefix)
427
429
5. Generate YAML document
@@ -432,9 +434,9 @@ Init is idempotent — re-running skips components that already exist (Caddy con
432
434
1. Parse YAML file
433
435
2. If passphrase provided: attempt decryption of all `enc:` values to validate passphrase (including SSH keys)
434
436
3. In **merge** mode (default): skip entries that already exist by same name
435
-
4. In **replace** mode: delete all existing servers and apps, then import
437
+
4. In **replace** mode: refuse with "stop or undeploy running apps before a replace import" unless every existing app is `draft`, `created` or `stopped`; otherwise undeploy all apps and delete all servers (the first error stops the import), then import
436
438
5. Create servers with SSH key from export (fallback to `"pending"` if key is empty — user must enter key manually)
437
-
6. Create apps (status = created, ready for deploy)
439
+
6. Create apps (status = created, ready for deploy) with one route per entry in `domains` (exports without `domains` get a route for `domain`)
438
440
7. Import secrets and config files
439
441
8. Redirect to servers page
440
442
@@ -548,8 +550,10 @@ All styles live in `internal/http/templates/layout.html` as a single `<style>` b
548
550
- Cloudflare API token requires minimal scope: Zone:DNS:Edit
549
551
- App containers on internal `dockify` Docker network only — no public port exposure except via Caddy
550
552
- Webhook authentication: HMAC-SHA256 (GitHub), token with constant-time compare (GitLab), auto-generated 64-char hex secret; always on (no disable), fails closed
551
-
- Session auth: cookie-based (`dockify_session`, `HttpOnly`, `SameSite=Lax`, `Secure` when the request is TLS or `X-Forwarded-Proto: https`), 24-hour expiry, in-memory sessions
553
+
- Session auth: cookie-based (`dockify_session`, `HttpOnly`, `SameSite=Lax`, `Secure` when the request is TLS or `X-Forwarded-Proto: https`), 24-hour expiry, in-memory sessions; expired sessions are dropped on each login
554
+
- Login: user and password compared in constant time; a failure waits 1 s; more than 10 failures from one client IP within 15 minutes (counted from the first) answer `429` until the window ends. The client IP is `RemoteAddr`; `chimw.RealIP` (trusting `X-Forwarded-For`/`X-Real-IP`) applies only with `DOCKIFY_TRUST_PROXY=true`
552
555
- Cross-site requests: unsafe methods from another site get `403` (Go `http.CrossOriginProtection`), WebSocket handshakes must be same-origin, no CORS headers. A reverse proxy in front of Dockify must forward the original `Host` header (Caddy does by default)
556
+
- Every response carries `Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self' ws: wss:; frame-ancestors 'none'; base-uri 'self'` (inline scripts/styles are used in the templates), `X-Content-Type-Options: nosniff` and `Referrer-Policy: same-origin`. Browser scripts come from the binary, not a CDN; only Google Fonts is external
0 commit comments