Simplify Docker self-hosting commands and env flow

This commit is contained in:
Ben Senescu 2026-03-09 23:20:23 -04:00
parent 2acf943346
commit 71da670924
8 changed files with 24 additions and 95 deletions

View File

@ -18,7 +18,7 @@ VITE_APP_ID=open-seo
# --- Docker self-hosting only (standalone local mode) --- # --- Docker self-hosting only (standalone local mode) ---
# See SELF_HOSTING_DOCKER.md # See SELF_HOSTING_DOCKER.md
# Docker compose runs with BYPASS_GATEWAY_LOCAL_ONLY=true. # Docker compose runs with BYPASS_GATEWAY_LOCAL_ONLY=true.
# Optional in .env.local because compose already sets it. # Optional in .env because compose already sets it.
# BYPASS_GATEWAY_LOCAL_ONLY=true # BYPASS_GATEWAY_LOCAL_ONLY=true
# Optional override (defaults to open-seo): # Optional override (defaults to open-seo):
# VITE_APP_ID=open-seo # VITE_APP_ID=open-seo

View File

@ -1,3 +0,0 @@
.env.local
.wrangler
node_modules/

View File

@ -146,9 +146,14 @@ Now you're all set! Go back to the gateway, click on the OpenSEO app, and start
## Docker Self Hosting (Standalone OpenSEO) ## Docker Self Hosting (Standalone OpenSEO)
If you want a single Docker Compose command that runs OpenSEO locally without Gateway auth, see [`SELF_HOSTING_DOCKER.md`](./SELF_HOSTING_DOCKER.md). Quickstart:
This runtime uses local dev servers to emulate Cloudflare Worker bindings. It is intended for **local use only** — do not expose ports directly to the public internet. For remote access, use [Tailscale](https://tailscale.com/). For internet-facing deployments, use the Cloudflare deployment path above. See the [security and runtime caveats](./SELF_HOSTING_DOCKER.md#security-and-runtime-caveats) in the Docker guide for details. 1. `cp .env.example .env`
2. Set `DATAFORSEO_API_KEY` in `.env`
3. `docker compose up --build`
4. Open `http://localhost:3001`
For runtime details, caveats, and troubleshooting, see [`SELF_HOSTING_DOCKER.md`](./SELF_HOSTING_DOCKER.md).
## Local Development ## Local Development

View File

@ -10,22 +10,21 @@ In this mode, OpenSEO runs with `BYPASS_GATEWAY_LOCAL_ONLY=true`, so authenticat
## Security and runtime caveats ## Security and runtime caveats
This stack runs local dev servers to emulate Cloudflare Worker bindings and is intended for local use only. This stack is local-first and uses dev runtimes to emulate Cloudflare Worker bindings.
- Do not expose these ports directly to the public internet. - Do not expose these ports directly to the public internet.
- There is no Gateway auth in this mode. - There is no built-in Gateway auth in this mode.
- If you need remote access, use [Tailscale](https://tailscale.com/) instead of a public tunnel. - If you expose it beyond localhost, put it behind the same authentication layer you use for your other self-hosted services (or use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes)).
- For internet-facing deployments with auth, use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes).
## 1) Configure env values ## 1) Configure env values
From the repository root: From the repository root:
```bash ```bash
cp .env.example .env.local cp .env.example .env
``` ```
Set values as needed in `.env.local`. Set values as needed in `.env`.
Required: Required:
@ -37,16 +36,10 @@ Optional:
- `VITE_APP_ID` (defaults to `open-seo`) - `VITE_APP_ID` (defaults to `open-seo`)
- `BYPASS_GATEWAY_LOCAL_ONLY=true` (Docker compose already sets this) - `BYPASS_GATEWAY_LOCAL_ONLY=true` (Docker compose already sets this)
Validate env before startup:
```bash
pnpm run docker:check-env
```
## 2) Start OpenSEO ## 2) Start OpenSEO
```bash ```bash
pnpm run docker:up docker compose up --build
``` ```
URL: URL:
@ -64,14 +57,14 @@ Boot behavior:
- OpenSEO env values seem stale: restart OpenSEO: - OpenSEO env values seem stale: restart OpenSEO:
```bash ```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local up -d --build open-seo docker compose up -d --build open-seo
``` ```
- If migrations fail on first run, rebuild and retry: - If migrations fail on first run, rebuild and retry:
```bash ```bash
pnpm run docker:down docker compose down
pnpm run docker:up docker compose up --build
``` ```
## Stop and cleanup ## Stop and cleanup
@ -79,11 +72,11 @@ pnpm run docker:up
Stop stack: Stop stack:
```bash ```bash
pnpm run docker:down docker compose down
``` ```
Stop and remove Docker volumes: Stop and remove Docker volumes:
```bash ```bash
docker compose -f self-host/docker-compose.yml --env-file .env.local down -v docker compose down -v
``` ```

View File

@ -1,7 +1,7 @@
services: services:
open-seo: open-seo:
build: build:
context: .. context: .
dockerfile: self-host/Dockerfile.selfhost dockerfile: self-host/Dockerfile.selfhost
working_dir: /app working_dir: /app
environment: environment:
@ -22,7 +22,7 @@ services:
ports: ports:
- "127.0.0.1:3001:3001" - "127.0.0.1:3001:3001"
volumes: volumes:
- ..:/app - .:/app
- open_seo_node_modules:/app/node_modules - open_seo_node_modules:/app/node_modules
- open_seo_pnpm_store:/pnpm/store - open_seo_pnpm_store:/pnpm/store

View File

@ -20,9 +20,8 @@
"db:migrate:prod": "npx everyapp app remote-d1-shell -- drizzle-kit migrate --config=drizzle-prod.config.ts", "db:migrate:prod": "npx everyapp app remote-d1-shell -- drizzle-kit migrate --config=drizzle-prod.config.ts",
"db:studio:local": "drizzle-kit studio", "db:studio:local": "drizzle-kit studio",
"db:studio:prod": "npx everyapp app remote-d1-shell -- drizzle-kit studio --config=drizzle-prod.config.ts", "db:studio:prod": "npx everyapp app remote-d1-shell -- drizzle-kit studio --config=drizzle-prod.config.ts",
"docker:check-env": "node ./self-host/scripts/validate-selfhost-env.mjs", "docker:up": "docker compose up --build",
"docker:up": "pnpm run docker:check-env && docker compose -f self-host/docker-compose.yml --env-file .env.local up --build", "docker:down": "docker compose down",
"docker:down": "docker compose -f self-host/docker-compose.yml --env-file .env.local down",
"docker:generate-secrets": "node ./self-host/scripts/generate-selfhost-secrets.mjs", "docker:generate-secrets": "node ./self-host/scripts/generate-selfhost-secrets.mjs",
"knip": "knip", "knip": "knip",
"ci": "prettier --check . && knip && tsc --noEmit && oxlint ." "ci": "prettier --check . && knip && tsc --noEmit && oxlint ."

View File

@ -12,7 +12,7 @@ const { privateKey, publicKey } = generateKeyPairSync("rsa", {
}); });
console.log( console.log(
"# Copy these lines into .env.local\n# See SELF_HOSTING_DOCKER.md for setup instructions", "# Copy these lines into .env\n# See SELF_HOSTING_DOCKER.md for setup instructions",
); );
console.log(`BETTER_AUTH_SECRET=${betterAuthSecret}`); console.log(`BETTER_AUTH_SECRET=${betterAuthSecret}`);
console.log(`JWT_PRIVATE_KEY="${escapeForEnv(privateKey)}"`); console.log(`JWT_PRIVATE_KEY="${escapeForEnv(privateKey)}"`);

View File

@ -1,65 +0,0 @@
import { existsSync, readFileSync } from "node:fs";
function parseEnvFile(path) {
const raw = readFileSync(path, "utf8");
const out = {};
for (const line of raw.split(/\r?\n/)) {
const trimmed = line.trim();
if (trimmed.length === 0 || trimmed.startsWith("#")) {
continue;
}
const equalsIndex = trimmed.indexOf("=");
if (equalsIndex <= 0) {
continue;
}
const key = trimmed.slice(0, equalsIndex).trim();
let value = trimmed.slice(equalsIndex + 1).trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
out[key] = value.replace(/\\n/g, "\n");
}
return out;
}
function isBlank(value) {
return typeof value !== "string" || value.trim().length === 0;
}
const envPath = process.argv[2] || ".env.local";
if (!existsSync(envPath)) {
console.error(`Missing env file: ${envPath}`);
console.error("Create it with: cp .env.example .env.local");
process.exit(1);
}
const env = parseEnvFile(envPath);
const requiredKeys = ["DATAFORSEO_API_KEY"];
const missingKeys = requiredKeys.filter((key) => isBlank(env[key]));
if (missingKeys.length > 0) {
console.error("Missing required keys in env file:");
for (const key of missingKeys) {
console.error(`- ${key}`);
}
process.exit(1);
}
if (isBlank(env.BYPASS_GATEWAY_LOCAL_ONLY)) {
console.warn(
"Warning: BYPASS_GATEWAY_LOCAL_ONLY is not set in .env.local. Docker sets it automatically.",
);
}
console.log(`Env validation passed: ${envPath}`);