Simplify Docker self-hosting commands and env flow
This commit is contained in:
parent
2acf943346
commit
71da670924
@ -18,7 +18,7 @@ VITE_APP_ID=open-seo
|
||||
# --- Docker self-hosting only (standalone local mode) ---
|
||||
# See SELF_HOSTING_DOCKER.md
|
||||
# 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
|
||||
# Optional override (defaults to open-seo):
|
||||
# VITE_APP_ID=open-seo
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
.env.local
|
||||
.wrangler
|
||||
node_modules/
|
||||
@ -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)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@ -10,22 +10,21 @@ In this mode, OpenSEO runs with `BYPASS_GATEWAY_LOCAL_ONLY=true`, so authenticat
|
||||
|
||||
## 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.
|
||||
- There is no Gateway auth in this mode.
|
||||
- If you need remote access, use [Tailscale](https://tailscale.com/) instead of a public tunnel.
|
||||
- For internet-facing deployments with auth, use the [Cloudflare deployment path](./README.md#self-hosting-deploy-on-cloudflare-5-10-minutes).
|
||||
- There is no built-in Gateway auth in this mode.
|
||||
- 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)).
|
||||
|
||||
## 1) Configure env values
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
cp .env.example .env.local
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Set values as needed in `.env.local`.
|
||||
Set values as needed in `.env`.
|
||||
|
||||
Required:
|
||||
|
||||
@ -37,16 +36,10 @@ Optional:
|
||||
- `VITE_APP_ID` (defaults to `open-seo`)
|
||||
- `BYPASS_GATEWAY_LOCAL_ONLY=true` (Docker compose already sets this)
|
||||
|
||||
Validate env before startup:
|
||||
|
||||
```bash
|
||||
pnpm run docker:check-env
|
||||
```
|
||||
|
||||
## 2) Start OpenSEO
|
||||
|
||||
```bash
|
||||
pnpm run docker:up
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
URL:
|
||||
@ -64,14 +57,14 @@ Boot behavior:
|
||||
- OpenSEO env values seem stale: restart OpenSEO:
|
||||
|
||||
```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:
|
||||
|
||||
```bash
|
||||
pnpm run docker:down
|
||||
pnpm run docker:up
|
||||
docker compose down
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
## Stop and cleanup
|
||||
@ -79,11 +72,11 @@ pnpm run docker:up
|
||||
Stop stack:
|
||||
|
||||
```bash
|
||||
pnpm run docker:down
|
||||
docker compose down
|
||||
```
|
||||
|
||||
Stop and remove Docker volumes:
|
||||
|
||||
```bash
|
||||
docker compose -f self-host/docker-compose.yml --env-file .env.local down -v
|
||||
docker compose down -v
|
||||
```
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
services:
|
||||
open-seo:
|
||||
build:
|
||||
context: ..
|
||||
context: .
|
||||
dockerfile: self-host/Dockerfile.selfhost
|
||||
working_dir: /app
|
||||
environment:
|
||||
@ -22,7 +22,7 @@ services:
|
||||
ports:
|
||||
- "127.0.0.1:3001:3001"
|
||||
volumes:
|
||||
- ..:/app
|
||||
- .:/app
|
||||
- open_seo_node_modules:/app/node_modules
|
||||
- open_seo_pnpm_store:/pnpm/store
|
||||
|
||||
@ -20,9 +20,8 @@
|
||||
"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: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": "pnpm run docker:check-env && docker compose -f self-host/docker-compose.yml --env-file .env.local up --build",
|
||||
"docker:down": "docker compose -f self-host/docker-compose.yml --env-file .env.local down",
|
||||
"docker:up": "docker compose up --build",
|
||||
"docker:down": "docker compose down",
|
||||
"docker:generate-secrets": "node ./self-host/scripts/generate-selfhost-secrets.mjs",
|
||||
"knip": "knip",
|
||||
"ci": "prettier --check . && knip && tsc --noEmit && oxlint ."
|
||||
|
||||
@ -12,7 +12,7 @@ const { privateKey, publicKey } = generateKeyPairSync("rsa", {
|
||||
});
|
||||
|
||||
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(`JWT_PRIVATE_KEY="${escapeForEnv(privateKey)}"`);
|
||||
|
||||
@ -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}`);
|
||||
Loading…
x
Reference in New Issue
Block a user