From ade78e4eb1c92177c0a41de6dcc9d76e3946729c Mon Sep 17 00:00:00 2001 From: Nazmus Samir Date: Wed, 29 Jul 2026 10:05:59 -0400 Subject: [PATCH] Configure self-hosted S3 CORS --- .env.example | 2 ++ doc/self-hosting.md | 6 +++++- docker-compose.yml | 32 +++++++++++++++++++++++++++++--- install.ps1 | 10 +++++++--- install.sh | 2 ++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/.env.example b/.env.example index 726f74601a..b76519cf2d 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,8 @@ # ── Public-facing ports (Caddy) --------------------------------------- HTTP_PORT=80 # HTTPS_PORT=443 # uncomment after you enable TLS in caddy/Caddyfile +PUTER_DOMAIN=puter.localhost +PUTER_PROTOCOL=http # ── MariaDB ------------------------------------------------------------ MARIADB_ROOT_PASSWORD=replace-with-strong-password diff --git a/doc/self-hosting.md b/doc/self-hosting.md index 84e3afd1cb..b638cae0cc 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -54,6 +54,8 @@ URL_SIGNATURE_SECRET=$(openssl rand -hex 64) cat > .env </`). Real AWS S3 wants virtual-hosted (`.`) — drop this flag (or set `false`) when you swap to real S3. - `s3.s3Config.publicEndpoint` — `endpoint` (`http://s3:9000`) only resolves inside the docker network; presigned upload/download URLs handed to the browser need a host-reachable URL. Caddy routes the `s3.` subdomain to RustFS internally and preserves the Host header end-to-end (required for S3 signature validation), so the browser hits the same port/protocol as the rest of the app — no separate published port, no mixed-content surprises when you turn on TLS. Switch to `https://s3.` once you enable TLS in Step 3. Real AWS S3 doesn't need this — its endpoint is already public; drop the field entirely. +- `PUTER_DOMAIN` + `PUTER_PROTOCOL` in `.env` — the `s3-init` container uses these values to apply a restricted bucket CORS policy for Puter's main, API, app, site, dev, and host origins. Keep them aligned with `domain` and `protocol` in `config.json`; `docker compose up -d` reapplies the policy safely. - `trust_proxy: 1` — Caddy terminates TLS and forwards `X-Forwarded-For`. Without this, `req.ip` is the docker-network address of the Caddy container instead of the real client IP, which breaks rate limiting and IP-based audit logs. `1` = one trusted hop (Caddy). Bump to `2` if you put Cloudflare in front of Caddy; never set `true` (it trusts every hop and makes XFF forgeable). > If you ever change `MARIADB_PASSWORD` after first boot, `.env` alone won't update MariaDB — its credentials are baked into `./puter/data/mariadb/` on first init. Either rotate the password inside MariaDB by hand or `docker compose down && rm -rf ./puter/data/mariadb` to start fresh. @@ -192,7 +195,8 @@ Drop the resulting `fullchain.pem` and `privkey.pem` into `./puter/tls/`. 2. (Optional but recommended) Replace the plain `:80 { import puter_routes }` block with the `redir` version shown alongside it, to force HTTPS everywhere. 3. In [docker-compose.yml](../docker-compose.yml), uncomment the `443:443` port mapping under the `caddy` service. 4. In `.env`, uncomment `HTTPS_PORT=443`. -5. In `config.json`, switch: +5. In `.env`, set `PUTER_PROTOCOL=https` so the S3 bucket CORS policy uses HTTPS origins. +6. In `config.json`, switch: ```json { "protocol": "https", "pub_port": 443 } ``` diff --git a/docker-compose.yml b/docker-compose.yml index 344555bfbf..be65d84249 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -139,8 +139,8 @@ services: start_period: 5s s3-init: - # One-shot container that creates the `puter-local` bucket on first - # boot. Exits 0 once the bucket exists; stays exited 0 thereafter. + # One-shot container that creates the `puter-local` bucket and applies + # the browser CORS policy. Both operations are idempotent. image: amazon/aws-cli:latest container_name: puter-s3-init depends_on: @@ -150,11 +150,13 @@ services: AWS_ACCESS_KEY_ID: ${S3_ACCESS_KEY:-puter} AWS_SECRET_ACCESS_KEY: ${S3_SECRET_KEY:-puter-secret-change-me} AWS_DEFAULT_REGION: us-east-1 + PUTER_DOMAIN: ${PUTER_DOMAIN:-puter.localhost} + PUTER_PROTOCOL: ${PUTER_PROTOCOL:-http} entrypoint: - /bin/sh - -c - | - set -e + set -eu endpoint=http://s3:9000 bucket=${S3_BUCKET:-puter-local} if aws --endpoint-url "$$endpoint" s3api head-bucket --bucket "$$bucket" 2>/dev/null; then @@ -163,6 +165,30 @@ services: echo "creating bucket $$bucket" aws --endpoint-url "$$endpoint" s3 mb "s3://$$bucket" fi + cat > /tmp/cors.json <