feat: add startup migration script and improve Docker setup
- Add scripts/start.ts to run drizzle migrations before server start - Update Dockerfile to copy migration files and install prod deps - Change entrypoint to use migration-aware startup script - Add deployment documentation
This commit is contained in:
@@ -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=<your-secure-db-password>
|
||||||
|
BETTER_AUTH_SECRET=<output from openssl>
|
||||||
|
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 |
|
||||||
+14
-1
@@ -20,6 +20,19 @@ WORKDIR /app
|
|||||||
# Only `.output` folder is needed from the build stage
|
# Only `.output` folder is needed from the build stage
|
||||||
COPY --from=build /app/.output /app
|
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
|
# run the app
|
||||||
EXPOSE 3000/tcp
|
EXPOSE 3000/tcp
|
||||||
ENTRYPOINT [ "bun", "--bun", "run", "/app/server/index.mjs" ]
|
ENTRYPOINT [ "bun", "run", "/app/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();
|
||||||
Reference in New Issue
Block a user