From e2a9a4336b225e376713037452c5fafffa7269ad Mon Sep 17 00:00:00 2001 From: hexdev Date: Mon, 17 Aug 2026 13:02:35 +0700 Subject: [PATCH] chore(deploy): adjust local user permissions and Caddy routing blocks --- compose.local.yml | 8 ++++ compose.production.yml | 2 + deploy/README.md | 9 +++-- deploy/caddy/Caddyfile | 20 ++++++---- deploy/caddy/Caddyfile.local | 74 +++++++++++++++++++----------------- 5 files changed, 67 insertions(+), 46 deletions(-) diff --git a/compose.local.yml b/compose.local.yml index 09a18b3..06f45e3 100644 --- a/compose.local.yml +++ b/compose.local.yml @@ -6,3 +6,11 @@ services: ports: !override - "8080:80" environment: !reset {} + + # Local PEM files are commonly mode 0600 and owned by the host user. + # Production keeps the backend non-root and uses keys owned by UID 10001. + backend: + user: "0:0" + + migrate: + user: "0:0" diff --git a/compose.production.yml b/compose.production.yml index dcfe545..df7ba7c 100644 --- a/compose.production.yml +++ b/compose.production.yml @@ -42,6 +42,7 @@ services: start_period: 20s networks: - private + - egress migrate: build: @@ -85,3 +86,4 @@ networks: public: private: internal: true + egress: diff --git a/deploy/README.md b/deploy/README.md index 0e04806..40e823b 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -1,8 +1,9 @@ # Production deployment This stack exposes only Caddy on ports 80 and 443. PostgreSQL and the FastAPI backend -are isolated in the private Docker network. Caddy serves the Vite SPA and proxies public -`/api/*` requests to the backend after removing the `/api` prefix. +share an internal Docker network. The backend additionally uses a non-published egress +network for DNS and outbound requests to Pally and Remnawave. Caddy serves the Vite SPA and +proxies public `/api/*` requests to the backend after removing the `/api` prefix. ## First deployment @@ -55,7 +56,9 @@ serves HTTP on `http://localhost:8080`. It does not need a domain, TLS certifica 1. Create a local `.env` from `.env.example` and fill the required backend integration settings. `POSTGRES_HOST=postgres`, `PRIVATE_KEY_FP=/run/secrets/keys/private.pem`, and `PUBLIC_KEY_FP=/run/secrets/keys/public.pem` must remain unchanged. The local `keys/` - directory must contain both PEM files. + directory must contain both PEM files. The local Compose override runs backend commands as + root only to read host-owned PEM files with mode `0600`; this override must not be used in + production. 2. Start PostgreSQL and apply migrations: ```bash diff --git a/deploy/caddy/Caddyfile b/deploy/caddy/Caddyfile index e88d19a..d3d1538 100644 --- a/deploy/caddy/Caddyfile +++ b/deploy/caddy/Caddyfile @@ -20,8 +20,9 @@ malenia.space { } @api path /api/* - route @api { - rate_limit { + handle @api { + route { + rate_limit { zone auth { match { path /api/auth/login /api/auth/signup /api/auth/refresh @@ -58,13 +59,16 @@ malenia.space { window 1m ipv6_prefix 64 } - } + } - uri strip_prefix /api - reverse_proxy backend:8000 + uri strip_prefix /api + reverse_proxy backend:8000 + } } - root * /srv - try_files {path} /index.html - file_server + handle { + root * /srv + try_files {path} /index.html + file_server + } } diff --git a/deploy/caddy/Caddyfile.local b/deploy/caddy/Caddyfile.local index 3504dc7..4c2860d 100644 --- a/deploy/caddy/Caddyfile.local +++ b/deploy/caddy/Caddyfile.local @@ -11,47 +11,51 @@ } @api path /api/* - route @api { - rate_limit { - zone auth { - match { - path /api/auth/login /api/auth/signup /api/auth/refresh + handle @api { + route { + rate_limit { + zone auth { + match { + path /api/auth/login /api/auth/signup /api/auth/refresh + } + key {remote_host} + events 10 + window 1m } - key {remote_host} - events 10 - window 1m - } - zone checkout { - match { - path /api/orders/checkout + zone checkout { + match { + path /api/orders/checkout + } + key {remote_host} + events 10 + window 10m } - key {remote_host} - events 10 - window 10m - } - zone payment_callback { - match { - path /api/payments/pally/result + zone payment_callback { + match { + path /api/payments/pally/result + } + key {remote_host} + events 60 + window 1m } - key {remote_host} - events 60 - window 1m - } - zone api { - match { - not path /api/health/ + zone api { + match { + not path /api/health/ + } + key {remote_host} + events 120 + window 1m } - key {remote_host} - events 120 - window 1m } - } - uri strip_prefix /api - reverse_proxy backend:8000 + uri strip_prefix /api + reverse_proxy backend:8000 + } } - root * /srv - try_files {path} /index.html - file_server + handle { + root * /srv + try_files {path} /index.html + file_server + } }