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) ---
|
# --- 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
|
||||||
|
|||||||
@ -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)
|
## 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
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
```
|
```
|
||||||
|
|||||||
@ -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
|
||||||
|
|
||||||
@ -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 ."
|
||||||
|
|||||||
@ -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)}"`);
|
||||||
|
|||||||
@ -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