build: tighten Docker setup for local-stack parity

- Dockerfile: npm ci (uses package-lock for reproducible installs)
- CMD now: migrate → seed (idempotent) → start. Gated by SEED_ON_BOOT.
- docker-compose: name: helix, healthcheck on /, OAuth env defaults to empty
  so `docker compose up` works without a .env (public pages render; sign-in
  fails until OAuth is configured).
- README: explicit "Run it locally — two ways" section. Docker first
  (production-like), native Node second. Documents port-conflict workaround
  and Gitea OAuth setup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Rene De Ren
2026-05-20 11:05:06 +02:00
parent c3d978a7eb
commit 103ba3cb5d
3 changed files with 76 additions and 19 deletions

View File

@@ -9,8 +9,8 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json package-lock.json* ./
RUN npm install --include=dev
COPY package.json package-lock.json ./
RUN npm ci --include=dev
COPY . .
RUN npm run build
@@ -39,5 +39,6 @@ COPY --from=build --chown=node:node /app/scripts ./scripts
USER node
EXPOSE 3000
# Run migrations then start the SvelteKit Node server.
CMD ["sh", "-c", "node scripts/migrate.js && node build"]
# Run migrations + seed (idempotent INSERT OR IGNORE) then start the server.
# Set SEED_ON_BOOT=false in production once you've added real content.
CMD ["sh", "-c", "node scripts/migrate.js && { [ \"$SEED_ON_BOOT\" = \"false\" ] || node scripts/seed.js; } && node build"]

View File

@@ -17,18 +17,57 @@ to the `RnD` Gitea organisation; viewing is open.
- **Gitea OAuth2** for authoring (no passwords stored)
- Pure **SVG + CSS** helix animation — no WebGL
## Local development
## Run it locally — two ways
### 1. Docker (production-like, recommended for testing)
Mirrors how the EVOLV stack is exercised locally. One command, named volume
for SQLite, healthcheck wired:
```bash
nvm use # picks up .nvmrc → Node 20
cp .env.example .env
# Fill in GITEA_CLIENT_ID and GITEA_CLIENT_SECRET (see "Gitea OAuth setup" below)
cd /path/to/helix
docker compose up -d --build # build image + start container
docker compose logs -f helix # tail logs
# → http://localhost:3000
```
If port 3000 is in use (Node-RED, another dev server, …) pick another:
```bash
HELIX_PORT=3030 \
ORIGIN=http://localhost:3030 \
GITEA_REDIRECT_URI=http://localhost:3030/auth/gitea/callback \
docker compose up -d --build
# → http://localhost:3030
```
Useful commands:
```bash
docker compose ps # status + health
docker inspect helix --format '{{.State.Health.Status}}'
docker compose exec helix sh # shell into the container
docker compose exec helix sqlite3 /data/helix.db # inspect the DB
docker compose down # stop (keeps the volume)
docker compose down -v # stop + DELETE all data
```
The SQLite database lives in the **`helix_helix-data`** named volume at
`/data/helix.db`. Migrations + the idempotent seed both run on every boot.
Once you have real content, set `SEED_ON_BOOT=false` in your env to stop
re-seeding the demo entries.
### 2. Native Node (faster dev loop, hot reload)
```bash
nvm use # .nvmrc → Node 20 (Tailwind v3 also works on 18)
cp .env.example .env # fill in Gitea OAuth — see below
npm install
npm run db:generate # produces drizzle/0000_*.sql from the schema
npm run db:migrate # applies migrations to ./helix.db
npm run db:seed # adds example projects + posts so the landing page isn't empty
npm run dev # http://localhost:3000
npm run db:generate # generate drizzle/0000_*.sql from the schema
npm run db:migrate # apply migrations to ./helix.db
npm run db:seed # idempotent demo content
npm run dev # http://localhost:5173
```
## Gitea OAuth setup
@@ -58,15 +97,23 @@ origins can be embedded inline on a project page. Add a hostname (no scheme,
no path) and redeploy. Hosts not on the list render as a "Open in new tab" card
instead — never blindly iframed.
## Production deploy (Docker)
## Production deploy
Same compose file as local. On the production host:
```bash
cp .env.example .env # fill in Gitea OAuth + ORIGIN
cp .env.example .env
# Set: ORIGIN=https://your-host, GITEA_REDIRECT_URI=https://your-host/auth/gitea/callback,
# GITEA_CLIENT_ID, GITEA_CLIENT_SECRET (from a separate prod OAuth app)
docker compose up -d --build
```
The SQLite database lives in the `helix-data` named volume at `/data/helix.db`.
Back it up with `docker compose exec helix sqlite3 /data/helix.db .dump`.
Put a TLS-terminating reverse proxy (nginx/caddy/traefik) in front of port 3000.
Back up the volume periodically:
```bash
docker compose exec helix sqlite3 /data/helix.db .dump | gzip > helix-backup-$(date +%F).sql.gz
```
## Project layout

View File

@@ -1,6 +1,9 @@
name: helix
services:
helix:
build: .
build:
context: .
container_name: helix
restart: unless-stopped
ports:
@@ -11,12 +14,18 @@ services:
ORIGIN: ${ORIGIN:-http://localhost:3000}
DATABASE_URL: /data/helix.db
GITEA_BASE_URL: ${GITEA_BASE_URL:-https://gitea.wbd-rd.nl}
GITEA_CLIENT_ID: ${GITEA_CLIENT_ID}
GITEA_CLIENT_SECRET: ${GITEA_CLIENT_SECRET}
GITEA_CLIENT_ID: ${GITEA_CLIENT_ID:-}
GITEA_CLIENT_SECRET: ${GITEA_CLIENT_SECRET:-}
GITEA_REDIRECT_URI: ${GITEA_REDIRECT_URI:-http://localhost:3000/auth/gitea/callback}
GITEA_ALLOWED_ORG: ${GITEA_ALLOWED_ORG:-RnD}
volumes:
- helix-data:/data
healthcheck:
test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:3000/').then(r => process.exit(r.ok ? 0 : 1)).catch(() => process.exit(1))"]
interval: 15s
timeout: 5s
retries: 4
start_period: 10s
volumes:
helix-data: