diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..e767923 --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,186 @@ +# Deploying Veridian + +Self-hosted deployment guide using Docker Compose on a VPS. + +## Prerequisites + +- A VPS with Docker and Docker Compose installed +- A domain name pointed at your VPS IP address +- An external reverse proxy (Caddy, Nginx, etc.) for HTTPS termination + +## First Deploy + +### 1. Clone the repository + +```bash +git clone https://gitea.wildcardproject.com/zoeissleeping/veridian.git +cd veridian +``` + +### 2. Configure environment variables + +```bash +cp .env.example .env +``` + +Edit `.env` and set the required values: + +```bash +# Generate a secure auth secret +openssl rand -base64 32 + +# Set these in .env +POSTGRES_PASSWORD= +BETTER_AUTH_SECRET= +NUXT_PUBLIC_URL=https://your-domain.com +``` + +### 3. Build and start + +```bash +docker compose up --build -d +``` + +This will: +1. Build the Nuxt application +2. Start PostgreSQL and wait for it to be healthy +3. Run all pending database migrations automatically +4. Start the application server + +### 4. Verify + +```bash +# Check container status +docker compose ps + +# Check app logs (should show migration + server start) +docker compose logs app + +# Check for errors +docker compose logs app | grep -i error +``` + +### 5. Set up your reverse proxy + +Point your reverse proxy at `127.0.0.1:3000`. Example Caddy config: + +``` +your-domain.com { + reverse_proxy 127.0.0.1:3000 +} +``` + +## Updating + +When you pull new changes: + +```bash +git pull + +# Rebuild and restart (migrations run automatically on startup) +docker compose up --build -d +``` + +The app container will: +1. Rebuild with the latest code +2. Run any new migrations that were added +3. Restart the server + +## Database Migrations + +Migrations run automatically every time the app container starts, via [`scripts/start.ts`](scripts/start.ts). This uses Drizzle ORM's programmatic migration API (`drizzle-orm/node-postgres/migrator`). + +- **First deploy**: All migrations run, creating all tables +- **Updates**: Only new/pending migrations run +- **No changes**: Migrations are a no-op + +If a migration fails, the app will not start and the container will exit with an error. Check logs with: + +```bash +docker compose logs app +``` + +### Creating new migrations + +After modifying [`drizzle/schema.ts`](drizzle/schema.ts): + +```bash +bunx drizzle-kit generate +``` + +This creates a new SQL file in `drizzle/migrations/`. Commit it to git. It will be applied automatically on next deploy. + +## Database Backups + +### Manual backup + +```bash +docker compose exec db pg_dump -U postgres veridian > backup_$(date +%Y%m%d).sql +``` + +### Restore from backup + +```bash +cat backup_20250101.sql | docker compose exec -T db psql -U postgres veridian +``` + +### Automated backups (optional) + +Add a cron job on the host: + +```bash +# Daily backup at 3 AM +0 3 * * * cd /path/to/veridian && docker compose exec -T db pg_dump -U postgres veridian | gzip > /var/backups/veridian/veridian_$(date +\%Y\%m\%d).sql.gz +``` + +## Troubleshooting + +### App won't start + +```bash +# Check logs +docker compose logs app + +# Common issues: +# - Missing .env file or required variables not set +# - Database not reachable (check db container is healthy) +# - Migration errors (schema conflicts) +``` + +### Database connection refused + +```bash +# Check DB is healthy +docker compose ps db + +# Check DB logs +docker compose logs db + +# Test connection +docker compose exec db psql -U postgres veridian -c "SELECT 1" +``` + +### Reset database (destructive) + +```bash +docker compose down -v # Removes volumes! +docker compose up --build -d +``` + +## Environment Variables Reference + +| Variable | Required | Description | +|----------|----------|-------------| +| `POSTGRES_DB` | No | Database name (default: `veridian`) | +| `POSTGRES_USER` | No | Database user (default: `postgres`) | +| `POSTGRES_PASSWORD` | **Yes** | Database password | +| `DATABASE_URL` | Auto | Built from the above vars by docker-compose | +| `BETTER_AUTH_SECRET` | **Yes** | Auth encryption secret (`openssl rand -base64 32`) | +| `NUXT_PUBLIC_URL` | **Yes** | Public URL (e.g., `https://veridian.example.com`) | +| `S3_ACCESS_KEY_ID` | No | S3-compatible storage access key | +| `S3_SECRET_ACCESS_KEY` | No | S3-compatible storage secret key | +| `S3_BUCKET_NAME` | No | S3 bucket name | +| `S3_REGION` | No | S3 region | +| `S3_ENDPOINT` | No | S3 endpoint URL | +| `DISABLE_SIGNUP` | No | Set to `true` to disable new user signups | +| `DISABLE_LOCAL_AUTH` | No | Set to `true` to disable email/password auth | diff --git a/Dockerfile b/Dockerfile index ac4e05a..ebbee2b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,6 +20,19 @@ WORKDIR /app # Only `.output` folder is needed from the build stage COPY --from=build /app/.output /app +# Copy migration SQL files for runtime migrations +COPY --from=build /app/drizzle/migrations /app/drizzle/migrations + +# Copy entrypoint script +COPY --from=build /app/scripts/start.ts /app/scripts/start.ts + +# Copy package.json and lockfile for production dependency install +COPY --from=build /app/package.json /app/package.json +COPY --from=build /app/bun.lock* ./ + +# Install production dependencies only (drizzle-orm, pg, etc.) +RUN bun install --frozen-lockfile --production --ignore-scripts + # run the app EXPOSE 3000/tcp -ENTRYPOINT [ "bun", "--bun", "run", "/app/server/index.mjs" ] +ENTRYPOINT [ "bun", "run", "/app/scripts/start.ts" ] diff --git a/scripts/start.ts b/scripts/start.ts new file mode 100644 index 0000000..3780b93 --- /dev/null +++ b/scripts/start.ts @@ -0,0 +1,27 @@ +import { migrate } from 'drizzle-orm/node-postgres/migrator'; +import { db } from '../server/lib/db'; +import { spawn } from 'child_process'; + +async function main() { + console.log('[start] Running database migrations...'); + + try { + await migrate(db, { migrationsFolder: './drizzle/migrations' }); + console.log('[start] Migrations complete.'); + } catch (err) { + console.error('[start] Migration failed:', err); + process.exit(1); + } + + console.log('[start] Starting server...'); + + const server = spawn('bun', ['--bun', 'run', '/app/server/index.mjs'], { + stdio: 'inherit', + }); + + server.on('exit', (code) => { + process.exit(code ?? 1); + }); +} + +main();