Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion doc/self-hosting.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ URL_SIGNATURE_SECRET=$(openssl rand -hex 64)
cat > .env <<EOF
HTTP_PORT=80
# HTTPS_PORT=443 # uncomment after enabling TLS in Step 3
PUTER_DOMAIN=puter.localhost
PUTER_PROTOCOL=http

MARIADB_ROOT_PASSWORD=$MARIADB_ROOT_PASSWORD
MARIADB_DATABASE=puter
Expand Down Expand Up @@ -142,6 +144,7 @@ Why these knobs:
- `providers.ollama.enabled: false` — Puter auto-probes a local Ollama at `127.0.0.1:11434` by default; without one running you'd see `ECONNREFUSED` on every boot. To run a bundled Ollama, see [Optional: local LLM (Ollama)](#optional-local-llm-ollama) below.
- `s3.s3Config.forcePathStyle: true` — RustFS / MinIO / fauxqs need path-style URLs (`<endpoint>/<bucket>`). Real AWS S3 wants virtual-hosted (`<bucket>.<endpoint>`) — 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.<domain>` 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.<your-domain>` 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.
Expand Down Expand Up @@ -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 }
```
Expand Down
32 changes: 29 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -163,6 +165,30 @@ services:
echo "creating bucket $$bucket"
aws --endpoint-url "$$endpoint" s3 mb "s3://$$bucket"
fi
cat > /tmp/cors.json <<EOF
{
"CORSRules": [
{
"AllowedOrigins": [
"$${PUTER_PROTOCOL}://$${PUTER_DOMAIN}",
"$${PUTER_PROTOCOL}://api.$${PUTER_DOMAIN}",
"$${PUTER_PROTOCOL}://app.$${PUTER_DOMAIN}",
"$${PUTER_PROTOCOL}://site.$${PUTER_DOMAIN}",
"$${PUTER_PROTOCOL}://dev.$${PUTER_DOMAIN}",
"$${PUTER_PROTOCOL}://host.$${PUTER_DOMAIN}"
],
"AllowedMethods": ["GET", "HEAD", "PUT", "POST", "DELETE"],
"AllowedHeaders": ["*"],
"ExposeHeaders": ["ETag", "x-amz-request-id"],
"MaxAgeSeconds": 3600
}
]
}
EOF
aws --endpoint-url "$$endpoint" s3api put-bucket-cors \
--bucket "$$bucket" \
--cors-configuration file:///tmp/cors.json
echo "bucket $$bucket CORS configured"
restart: "no"

# ── Optional: local LLM ───────────────────────────────────────────
Expand Down
10 changes: 7 additions & 3 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
# PUTER_URL base URL to fetch docker-compose.yml (default: GitHub raw, main branch)
# PUTER_DOMAIN domain Puter will serve on (default: puter.localhost)
# PUTER_PORT HTTP port for Caddy (default: 80)
# PUTER_PROTOCOL public scheme: http | https (default: http)
# PUTER_FORCE set to 1 to overwrite existing .env / config.json

[CmdletBinding()]
Expand All @@ -32,6 +33,7 @@ param(
[string]$PuterUrl = $(if ($env:PUTER_URL) { $env:PUTER_URL } else { 'https://raw.githubusercontent.com/HeyPuter/puter/main' }),
[string]$PuterDomain = $(if ($env:PUTER_DOMAIN) { $env:PUTER_DOMAIN } else { 'puter.localhost' }),
[int] $PuterPort = $(if ($env:PUTER_PORT) { [int]$env:PUTER_PORT } else { 80 }),
[string]$PuterProtocol = $(if ($env:PUTER_PROTOCOL) { $env:PUTER_PROTOCOL } else { 'http' }),
[switch]$Force = $($env:PUTER_FORCE -eq '1')
)

Expand Down Expand Up @@ -125,6 +127,8 @@ if ($writeConfig) {
HTTP_PORT=$PuterPort
# HTTPS_PORT=443 # uncomment after enabling TLS in caddy/Caddyfile
# # (see "Step 3 — TLS" in doc/self-hosting.md)
PUTER_DOMAIN=$PuterDomain
PUTER_PROTOCOL=$PuterProtocol

MARIADB_ROOT_PASSWORD=$mariadbRootPw
MARIADB_DATABASE=puter
Expand All @@ -140,7 +144,7 @@ S3_BUCKET=puter-local
Write-Log 'writing puter/config/config.json'
$config = [ordered]@{
domain = $PuterDomain
protocol = 'http'
protocol = $PuterProtocol
pub_port = $PuterPort
env = 'prod'
static_hosting_domain = "site.$PuterDomain"
Expand Down Expand Up @@ -178,7 +182,7 @@ S3_BUCKET=puter-local
s3 = [ordered]@{
s3Config = [ordered]@{
endpoint = 'http://s3:9000'
publicEndpoint = "http://s3.$PuterDomain"
publicEndpoint = "${PuterProtocol}://s3.$PuterDomain"
accessKeyId = 'puter'
secretAccessKey = $s3SecretKey
region = 'us-east-1'
Expand Down Expand Up @@ -206,6 +210,6 @@ Write-Log 'stack starting. first boot takes ~30s while MariaDB initialises.'
Write-Log 'follow puter logs:'
Write-Log " cd $PuterDir; docker compose logs -f puter"
Write-Log ''
Write-Log "open http://${PuterDomain}:${PuterPort} once the puter container is healthy."
Write-Log "open ${PuterProtocol}://${PuterDomain}:${PuterPort} once the puter container is healthy."
Write-Log 'first-boot admin password is logged once — grab it with:'
Write-Log " cd $PuterDir; docker compose logs puter | Select-String password"
2 changes: 2 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ if [ "$write_config" = "1" ]; then
HTTP_PORT=$PUTER_PORT
# HTTPS_PORT=443 # uncomment after enabling TLS in caddy/Caddyfile
# # (see "Step 3 — TLS" in doc/self-hosting.md)
PUTER_DOMAIN=$PUTER_DOMAIN
PUTER_PROTOCOL=$PUTER_PROTOCOL

MARIADB_ROOT_PASSWORD=$MARIADB_ROOT_PASSWORD
MARIADB_DATABASE=puter
Expand Down