feat: migrate from PocketBase to PostgreSQL with Drizzle ORM
- Add @project-e/db package with Drizzle schema and migrations - Replace PocketBase client with PostgreSQL-based database client - Migrate auth from custom to NextAuth.js - Add Docker Compose with PostgreSQL container - Update worker to use new database client - Remove PocketBase-specific files and migrations - Add drizzle config and initial migration
This commit is contained in:
+66
-547
@@ -1,404 +1,126 @@
|
||||
# Deployment Guide
|
||||
|
||||
Deploy Project E with Docker Compose and Nginx Proxy Manager. Three containers run the application: web (Next.js), db (PocketBase), and worker (background jobs).
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Quick Deploy](#quick-deploy)
|
||||
- [Environment Configuration](#environment-configuration)
|
||||
- [Nginx Proxy Manager Setup](#nginx-proxy-manager-setup)
|
||||
- [PocketBase Setup](#pocketbase-setup)
|
||||
- [Backups](#backups)
|
||||
- [Monitoring](#monitoring)
|
||||
- [Scaling](#scaling)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
Deploy Project E with Docker Compose and a reverse proxy such as Nginx Proxy Manager. Compose runs three services: the Next.js web app, PostgreSQL 16, and the background worker.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Docker** 24.0 or later
|
||||
- **Docker Compose** 2.20 or later
|
||||
- **Nginx Proxy Manager** installed and running
|
||||
- **At least 1GB RAM** and 10GB disk space
|
||||
- Docker 24.0 or later
|
||||
- Docker Compose 2.20 or later
|
||||
- A reverse proxy for TLS and public routing
|
||||
- At least 1 GB RAM and 10 GB disk space
|
||||
|
||||
## Quick Deploy
|
||||
## Deploy Project E
|
||||
|
||||
1. **Clone the repository on your server**
|
||||
1. Clone the repository on the server.
|
||||
|
||||
```bash
|
||||
git clone <repository-url>
|
||||
cd ProjectE
|
||||
```
|
||||
|
||||
2. **Create the environment file**
|
||||
2. Create the environment file.
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and set the required variables:
|
||||
3. Set the required values in `.env`.
|
||||
|
||||
```bash
|
||||
POCKETBASE_ADMIN_TOKEN=your-secure-random-token
|
||||
PUBLIC_URL=http://project-e.local
|
||||
COOKIE_SECURE=false
|
||||
ALLOWED_HOSTS=project-e.local,localhost
|
||||
POSTGRES_PASSWORD=your_postgres_password
|
||||
DATABASE_URL=postgresql://project_e:your_postgres_password@localhost:5432/project_e
|
||||
NEXTAUTH_SECRET=your_long_random_secret
|
||||
INITIAL_ADMIN_EMAIL=admin@example.com
|
||||
INITIAL_ADMIN_PASSWORD=your_initial_admin_password
|
||||
PUBLIC_URL=https://project-e.example.com
|
||||
COOKIE_SECURE=true
|
||||
ALLOWED_HOSTS=project-e.example.com
|
||||
```
|
||||
|
||||
Generate a secure token:
|
||||
Generate secrets with `openssl rand -base64 32`. Use the same `POSTGRES_PASSWORD` in `DATABASE_URL`. The web and worker containers use an internal database URL that Compose builds from `POSTGRES_PASSWORD`.
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
3. **Build and start containers**
|
||||
4. Build and start the services.
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
4. **Verify all containers are running**
|
||||
5. Confirm that the services are healthy.
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
You should see three containers with status `Up (healthy)`:
|
||||
- `project-e-web` (internal only, no exposed ports)
|
||||
- `project-e-db` (internal only, no exposed ports)
|
||||
- `project-e-worker` running in the background
|
||||
6. Route your domain to `project-e-web:3000` through your reverse proxy.
|
||||
|
||||
5. **Configure Nginx Proxy Manager** (see below)
|
||||
7. Open the app and sign in with `INITIAL_ADMIN_EMAIL` and `INITIAL_ADMIN_PASSWORD`. Project E creates that account only when the database contains no users.
|
||||
|
||||
6. **Create your admin account**
|
||||
## Environment variables
|
||||
|
||||
Once NPM is configured, access Project E through your domain. Create your first user account.
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `DATABASE_URL` | Yes | PostgreSQL connection string for local tools and processes outside Compose. |
|
||||
| `POSTGRES_PASSWORD` | Yes | Password for the `project_e` PostgreSQL user. |
|
||||
| `NEXTAUTH_SECRET` | Yes | Secret used to sign NextAuth JWT sessions. |
|
||||
| `INITIAL_ADMIN_EMAIL` | Yes | Email address for the first account. |
|
||||
| `INITIAL_ADMIN_PASSWORD` | Yes | Password for the first account. |
|
||||
| `PUBLIC_URL` | No | Public application URL. Defaults to `http://localhost:3000`. |
|
||||
| `COOKIE_SECURE` | No | Set to `true` behind HTTPS. Defaults to `false`. |
|
||||
| `ALLOWED_HOSTS` | No | Comma-separated allowed hostnames. |
|
||||
|
||||
## Environment Configuration
|
||||
Keep `.env` out of version control. Rotate `NEXTAUTH_SECRET` only when you intend to end active sessions.
|
||||
|
||||
### Required Variables
|
||||
## PostgreSQL and Drizzle
|
||||
|
||||
| Variable | Description |
|
||||
|----------|-------------|
|
||||
| `POCKETBASE_ADMIN_TOKEN` | Admin token from PocketBase. Required for the worker and server-side API operations. |
|
||||
The `db` service runs `postgres:16-alpine`, stores its data in the `project-e-pg-data` Docker volume, and exposes port 5432. The web and worker services connect to `db:5432` over the Compose network.
|
||||
|
||||
### Optional Variables
|
||||
Docker mounts `drizzle/0000_first_mauler.sql` into PostgreSQL's initialization directory. PostgreSQL runs that file only while it initializes an empty data volume. For a later Drizzle migration, apply the reviewed SQL as part of your release process. Do not expect a container restart to apply a new migration to an existing volume.
|
||||
|
||||
| Variable | Default | Description |
|
||||
|----------|---------|-------------|
|
||||
| `POCKETBASE_URL` | `http://db:8090` | PocketBase URL (internal Docker network) |
|
||||
| `NODE_ENV` | `production` | Node environment |
|
||||
| `PUBLIC_URL` | `http://localhost:3000` | Public URL where users access Project E (used for generating absolute URLs) |
|
||||
| `COOKIE_SECURE` | `false` | Set to `true` if using HTTPS through NPM, `false` for HTTP-only LAN access |
|
||||
| `ALLOWED_HOSTS` | `localhost` | Comma-separated list of domains that can access the app |
|
||||
|
||||
### Setting Variables
|
||||
|
||||
Create a `.env` file in the project root:
|
||||
To apply a migration from the host, run:
|
||||
|
||||
```bash
|
||||
POCKETBASE_ADMIN_TOKEN=abc123def456...
|
||||
POCKETBASE_URL=http://db:8090
|
||||
PUBLIC_URL=http://project-e.local
|
||||
COOKIE_SECURE=false
|
||||
ALLOWED_HOSTS=project-e.local,localhost
|
||||
docker compose exec -T db psql -U project_e -d project_e < drizzle/<migration>.sql
|
||||
```
|
||||
|
||||
Docker Compose reads this file automatically.
|
||||
Back up the database before applying schema changes.
|
||||
|
||||
## Nginx Proxy Manager Setup
|
||||
## Nginx Proxy Manager
|
||||
|
||||
### Add Proxy Host
|
||||
|
||||
1. Open Nginx Proxy Manager admin interface
|
||||
2. Go to **Hosts** → **Proxy Hosts** → **Add Proxy Host**
|
||||
|
||||
3. Configure the following:
|
||||
|
||||
**Details Tab:**
|
||||
- **Domain Names:** `project-e.local` (or your chosen domain)
|
||||
- **Scheme:** `http`
|
||||
- **Forward Hostname/IP:** `project-e-web` (the Docker container name)
|
||||
- **Forward Port:** `3000`
|
||||
|
||||
**SSL Tab:**
|
||||
- If using HTTPS: Select your SSL certificate
|
||||
- If HTTP-only on LAN: Leave SSL disabled
|
||||
|
||||
**Advanced Tab:**
|
||||
Add these custom Nginx configuration lines:
|
||||
1. In Nginx Proxy Manager, open **Hosts** > **Proxy Hosts** > **Add Proxy Host**.
|
||||
2. Set the domain name, choose `http`, set the forward hostname to `project-e-web`, and set the forward port to `3000`.
|
||||
3. Select a certificate and force SSL for HTTPS deployments.
|
||||
4. Add the following advanced configuration to support Server-Sent Events:
|
||||
|
||||
```nginx
|
||||
# WebSocket support for realtime features
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
|
||||
# Forward real client information
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_set_header Host $host;
|
||||
|
||||
# SSE support for realtime endpoint
|
||||
proxy_buffering off;
|
||||
proxy_cache off;
|
||||
proxy_read_timeout 300s;
|
||||
|
||||
# Increase timeout for long-running requests
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
```
|
||||
|
||||
4. Click **Save**
|
||||
5. Save the host and open the configured domain. You should reach the Project E sign-in page.
|
||||
|
||||
### Testing the Connection
|
||||
## Back up and restore PostgreSQL
|
||||
|
||||
1. From a device on your LAN, open a browser
|
||||
2. Navigate to `http://project-e.local` (or your configured domain)
|
||||
3. You should see the Project E login page
|
||||
|
||||
### Optional: PocketBase Admin Access
|
||||
|
||||
If you need direct access to the PocketBase admin UI:
|
||||
|
||||
1. In NPM, add another Proxy Host:
|
||||
- **Domain Names:** `pb.project-e.local`
|
||||
- **Scheme:** `http`
|
||||
- **Forward Hostname/IP:** `project-e-db`
|
||||
- **Forward Port:** `8090`
|
||||
|
||||
2. Or temporarily expose the port in `docker-compose.yml`:
|
||||
```yaml
|
||||
db:
|
||||
ports:
|
||||
- "8090:8090"
|
||||
```
|
||||
|
||||
## PocketBase Setup
|
||||
|
||||
### Initial Configuration
|
||||
|
||||
PocketBase runs as a standalone container. After starting it for the first time:
|
||||
|
||||
1. Access the admin UI at `http://your-server:8090/_/`
|
||||
2. Create your admin account
|
||||
3. Configure authentication settings under **Settings > Auth**
|
||||
4. Enable the auth methods you need (email/password is enabled by default)
|
||||
|
||||
### Migrations
|
||||
|
||||
Database migrations are in `pocketbase/pb_migrations/`. They run automatically when the PocketBase container starts.
|
||||
|
||||
To add a new migration:
|
||||
|
||||
1. Create a file in `pocketbase/pb_migrations/` with the naming convention `YYYYMMDDHHMMSS_description.js`
|
||||
2. Restart the PocketBase container: `docker compose restart db`
|
||||
|
||||
### Data Directory
|
||||
|
||||
PocketBase stores all data (SQLite database, uploads, logs) in the `/pb_data` volume. This volume persists across container restarts.
|
||||
|
||||
## Reverse Proxy
|
||||
|
||||
Put a reverse proxy in front of the application to handle SSL, compression, and routing.
|
||||
|
||||
### Caddy (Recommended)
|
||||
|
||||
Caddy handles SSL automatically.
|
||||
|
||||
Create a `Caddyfile`:
|
||||
|
||||
```
|
||||
your-domain.com {
|
||||
reverse_proxy localhost:3000
|
||||
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
X-Frame-Options "DENY"
|
||||
X-Content-Type-Options "nosniff"
|
||||
}
|
||||
}
|
||||
|
||||
pb.your-domain.com {
|
||||
reverse_proxy localhost:8090
|
||||
}
|
||||
```
|
||||
|
||||
Install and run Caddy:
|
||||
Create a logical backup without stopping the database:
|
||||
|
||||
```bash
|
||||
# Install Caddy
|
||||
sudo apt install caddy # Debian/Ubuntu
|
||||
# or
|
||||
brew install caddy # macOS
|
||||
|
||||
# Start Caddy
|
||||
caddy start
|
||||
mkdir -p backups
|
||||
```
|
||||
|
||||
### Traefik
|
||||
|
||||
Create a `traefik.yml`:
|
||||
|
||||
```yaml
|
||||
entryPoints:
|
||||
web:
|
||||
address: ":80"
|
||||
http:
|
||||
redirections:
|
||||
entryPoint:
|
||||
to: websecure
|
||||
scheme: https
|
||||
websecure:
|
||||
address: ":443"
|
||||
|
||||
certificatesResolvers:
|
||||
letsencrypt:
|
||||
acme:
|
||||
email: your-email@example.com
|
||||
storage: acme.json
|
||||
httpChallenge:
|
||||
entryPoint: web
|
||||
|
||||
providers:
|
||||
docker:
|
||||
exposedByDefault: false
|
||||
```
|
||||
|
||||
Update `docker-compose.yml` to add Traefik labels:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
web:
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.web.rule=Host(`your-domain.com`)"
|
||||
- "traefik.http.routers.web.entrypoints=websecure"
|
||||
- "traefik.http.routers.web.tls.certresolver=letsencrypt"
|
||||
```
|
||||
|
||||
### Nginx
|
||||
|
||||
Create `/etc/nginx/sites-available/project-e`:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name your-domain.com;
|
||||
return 301 https://$server_name$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
server_name your-domain.com;
|
||||
|
||||
ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# SSE support for realtime endpoint
|
||||
proxy_buffering off;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Enable the site and reload:
|
||||
Restore a backup into a new or empty database:
|
||||
|
||||
```bash
|
||||
sudo ln -s /etc/nginx/sites-available/project-e /etc/nginx/sites-enabled/
|
||||
sudo nginx -t
|
||||
sudo systemctl reload nginx
|
||||
docker compose exec -T db psql -U project_e -d project_e < backups/project-e-YYYYMMDD_HHMMSS.sql
|
||||
```
|
||||
|
||||
## SSL/TLS
|
||||
|
||||
### Let's Encrypt with Certbot
|
||||
|
||||
```bash
|
||||
# Install certbot
|
||||
sudo apt install certbot python3-certbot-nginx
|
||||
|
||||
# Get certificate
|
||||
sudo certbot --nginx -d your-domain.com -d pb.your-domain.com
|
||||
|
||||
# Auto-renewal is configured automatically
|
||||
```
|
||||
|
||||
### Caddy
|
||||
|
||||
Caddy obtains and renews certificates automatically. No configuration needed beyond the domain name in the `Caddyfile`.
|
||||
|
||||
### Internal Communication
|
||||
|
||||
The web and worker containers connect to PocketBase over the internal Docker network (`http://db:8090`). This traffic does not need SSL.
|
||||
|
||||
Only expose ports 3000 and 8090 to the reverse proxy, not directly to the internet.
|
||||
|
||||
## Backups
|
||||
|
||||
### PocketBase Database
|
||||
|
||||
The database is a single SQLite file at `/pb_data/data.db`.
|
||||
|
||||
**Manual backup:**
|
||||
|
||||
```bash
|
||||
# Stop the container to ensure consistency
|
||||
docker compose stop db
|
||||
|
||||
# Copy the database file
|
||||
docker cp project-e-db:/pb_data/data.db ./backups/data-$(date +%Y%m%d).db
|
||||
|
||||
# Restart the container
|
||||
docker compose start db
|
||||
```
|
||||
|
||||
**Automated backup script:**
|
||||
|
||||
Create `scripts/backup.sh`:
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
BACKUP_DIR="/path/to/backups"
|
||||
DATE=$(date +%Y%m%d_%H%M%S)
|
||||
|
||||
mkdir -p $BACKUP_DIR
|
||||
|
||||
# Use PocketBase's backup API (no downtime)
|
||||
curl -X POST http://localhost:8090/api/backup \
|
||||
-H "Authorization: Admin your-admin-token" \
|
||||
-o "$BACKUP_DIR/backup-$DATE.zip"
|
||||
|
||||
# Keep only last 30 backups
|
||||
find $BACKUP_DIR -name "backup-*.zip" -mtime +30 -delete
|
||||
|
||||
echo "Backup completed: backup-$DATE.zip"
|
||||
```
|
||||
|
||||
Schedule with cron:
|
||||
|
||||
```bash
|
||||
# Run daily at 2 AM
|
||||
0 2 * * * /path/to/scripts/backup.sh
|
||||
```
|
||||
|
||||
### Upload Files
|
||||
|
||||
Uploaded files are stored in the `project-e-web-uploads` volume.
|
||||
|
||||
**Backup uploads:**
|
||||
Back up the `project-e-web-uploads` volume if your deployment stores uploads there:
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
@@ -408,232 +130,29 @@ docker run --rm \
|
||||
tar czf /backup/uploads-$(date +%Y%m%d).tar.gz -C /source .
|
||||
```
|
||||
|
||||
### Restore
|
||||
Test restores on a separate environment before relying on a backup.
|
||||
|
||||
**Restore database:**
|
||||
## Monitoring and logs
|
||||
|
||||
```bash
|
||||
# Stop containers
|
||||
docker compose stop db
|
||||
|
||||
# Remove old data
|
||||
docker volume rm project-e-pb-data
|
||||
|
||||
# Copy backup into new volume
|
||||
docker volume create project-e-pb-data
|
||||
docker run --rm \
|
||||
-v project-e-pb-data:/pb_data \
|
||||
-v $(pwd)/backups:/backup \
|
||||
alpine \
|
||||
sh -c "cp /backup/data-YYYYMMDD.db /pb_data/data.db"
|
||||
|
||||
# Restart
|
||||
docker compose start db
|
||||
```
|
||||
|
||||
**Restore uploads:**
|
||||
|
||||
```bash
|
||||
docker run --rm \
|
||||
-v project-e-web-uploads:/target \
|
||||
-v $(pwd)/backups:/backup \
|
||||
alpine \
|
||||
sh -c "cd /target && tar xzf /backup/uploads-YYYYMMDD.tar.gz"
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Container Health
|
||||
|
||||
All containers include health checks. Check status:
|
||||
Check service status and logs:
|
||||
|
||||
```bash
|
||||
docker compose ps
|
||||
```
|
||||
|
||||
### Application Health
|
||||
|
||||
The web container exposes a health endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:3000/api/health
|
||||
```
|
||||
|
||||
**Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2024-01-15T10:30:00.000Z",
|
||||
"version": "0.1.0"
|
||||
}
|
||||
```
|
||||
|
||||
### Logs
|
||||
|
||||
View logs from all containers:
|
||||
|
||||
```bash
|
||||
# All containers
|
||||
docker compose logs -f
|
||||
|
||||
# Specific container
|
||||
docker compose logs -f web
|
||||
docker compose logs -f db
|
||||
docker compose logs -f worker
|
||||
```
|
||||
|
||||
### Resource Usage
|
||||
|
||||
Monitor container resource usage:
|
||||
|
||||
```bash
|
||||
docker stats
|
||||
```
|
||||
|
||||
### Uptime Monitoring
|
||||
|
||||
Use an external service to monitor your deployment:
|
||||
|
||||
- **UptimeRobot** (free tier): HTTP monitoring with email/SMS alerts
|
||||
- **Healthchecks.io**: Cron job monitoring
|
||||
- **Better Stack**: Status pages and incident management
|
||||
|
||||
Set up a check for `https://your-domain.com/api/health` with a 60-second interval.
|
||||
|
||||
## Scaling
|
||||
|
||||
### Vertical Scaling
|
||||
|
||||
The application runs as a single instance of each container. To handle more load:
|
||||
|
||||
1. **Increase server resources**: Add more CPU and RAM to your host
|
||||
2. **Increase Node.js memory**: Set `NODE_OPTIONS=--max-old-space-size=4096` in the web container
|
||||
3. **Increase PocketBase limits**: PocketBase handles thousands of concurrent connections on modest hardware
|
||||
|
||||
### Horizontal Scaling
|
||||
|
||||
Horizontal scaling requires changes to the architecture:
|
||||
|
||||
**Current limitations:**
|
||||
- MCP sessions are stored in memory (not shared between instances)
|
||||
- SSE connections are tied to a specific container
|
||||
- File uploads go to a local volume
|
||||
|
||||
**To scale horizontally:**
|
||||
1. Use a shared session store (Redis)
|
||||
2. Use a load balancer with sticky sessions for SSE
|
||||
3. Use object storage (S3) for file uploads
|
||||
4. Run multiple web containers behind a load balancer
|
||||
|
||||
For most personal and small-team use cases, a single instance handles the load. PocketBase with SQLite performs well up to hundreds of concurrent users.
|
||||
|
||||
### Worker Scaling
|
||||
|
||||
The worker uses polling with exponential backoff. For high-throughput job processing:
|
||||
|
||||
1. Run multiple worker containers (they coordinate through the database)
|
||||
2. Reduce the base poll interval (currently 5 seconds)
|
||||
3. Use a dedicated job queue (Bull, BullMQ) instead of database polling
|
||||
The web service exposes `GET /api/health` on port 3000. Configure uptime monitoring for `https://your-domain.example/api/health`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Container Won't Start
|
||||
| Problem | Fix |
|
||||
|---------|-----|
|
||||
| Database connection fails | Confirm that `db` is healthy and that `POSTGRES_PASSWORD` matches the value in `DATABASE_URL`. |
|
||||
| The web service will not start | Set `NEXTAUTH_SECRET`, `INITIAL_ADMIN_EMAIL`, and `INITIAL_ADMIN_PASSWORD` in `.env`, then restart the service. |
|
||||
| Sign-in fails | Confirm the email and password match the initial-admin values. Those values create an account only before any user exists. |
|
||||
| A schema change is missing | Apply the generated Drizzle SQL. PostgreSQL initialization scripts do not rerun for an existing volume. |
|
||||
| Realtime updates stop | Disable proxy buffering and set a read timeout of at least 300 seconds. |
|
||||
| Disk space runs low | Inspect `project-e-pg-data` and `project-e-web-uploads`, back up data, and expand the host disk. |
|
||||
|
||||
**Check logs:**
|
||||
## Scaling
|
||||
|
||||
```bash
|
||||
docker compose logs web
|
||||
docker compose logs db
|
||||
docker compose logs worker
|
||||
```
|
||||
|
||||
**Common issues:**
|
||||
|
||||
| Problem | Solution |
|
||||
|---------|----------|
|
||||
| `Cannot connect to PocketBase` | Ensure the `db` container is healthy. Check `docker compose ps`. |
|
||||
| `Port 3000 already in use` | Change the port mapping in `docker-compose.yml` |
|
||||
| `POCKETBASE_ADMIN_TOKEN not set` | Set the token in `.env` and restart |
|
||||
| `Migration failed` | Check migration files in `pocketbase/pb_migrations/` |
|
||||
|
||||
### Web App Returns 500 Errors
|
||||
|
||||
1. Check the web container logs: `docker compose logs web`
|
||||
2. Verify PocketBase is running: `docker compose exec db wget -qO- http://localhost:8090/api/health`
|
||||
3. Verify the admin token is correct: `docker compose exec web env | grep POCKETBASE`
|
||||
|
||||
### Realtime Events Not Arriving
|
||||
|
||||
1. Check the SSE connection: `curl -N http://localhost:3000/api/realtime`
|
||||
2. Verify the reverse proxy is not buffering SSE responses
|
||||
3. For Nginx, ensure `proxy_buffering off` is set
|
||||
4. Check browser console for connection errors
|
||||
|
||||
### Worker Not Processing Jobs
|
||||
|
||||
1. Check worker logs: `docker compose logs worker`
|
||||
2. Verify the admin token is set correctly
|
||||
3. Check for pending jobs in PocketBase admin: `http://localhost:8090/_/#/collections/queue_jobs`
|
||||
4. Jobs retry automatically with exponential backoff (max 5 minutes between retries)
|
||||
|
||||
### Database Corruption
|
||||
|
||||
If the SQLite database becomes corrupted:
|
||||
|
||||
1. Stop all containers: `docker compose down`
|
||||
2. Restore from the most recent backup (see [Backups](#backups))
|
||||
3. If no backup exists, try SQLite's recovery:
|
||||
|
||||
```bash
|
||||
sqlite3 data.db ".recover" | sqlite3 new-data.db
|
||||
```
|
||||
|
||||
4. Replace the corrupted file and restart
|
||||
|
||||
### Out of Disk Space
|
||||
|
||||
PocketBase stores the database and uploads in the `project-e-pb-data` volume.
|
||||
|
||||
**Check disk usage:**
|
||||
|
||||
```bash
|
||||
docker system df
|
||||
docker volume inspect project-e-pb-data
|
||||
```
|
||||
|
||||
**Clean up:**
|
||||
|
||||
```bash
|
||||
# Remove unused images
|
||||
docker image prune -a
|
||||
|
||||
# Remove unused volumes (WARNING: deletes all data)
|
||||
docker volume prune
|
||||
```
|
||||
|
||||
**Expand volume:**
|
||||
|
||||
Docker volumes use the host filesystem. If the host disk is full, expand it or move the volume to a larger disk.
|
||||
|
||||
### SSL Certificate Errors
|
||||
|
||||
**Caddy:** Check the Caddy logs for ACME errors. Ensure port 80 is accessible from the internet for the HTTP challenge.
|
||||
|
||||
**Let's Encrypt:** Certificates renew automatically. Force renewal:
|
||||
|
||||
```bash
|
||||
sudo certbot renew --force-renewal
|
||||
```
|
||||
|
||||
**Common errors:**
|
||||
- `Connection refused`: Port 80 is blocked by firewall
|
||||
- `DNS problem`: Domain does not resolve to this server
|
||||
- `Rate limit exceeded`: Too many certificate requests. Wait and retry.
|
||||
|
||||
### Performance Issues
|
||||
|
||||
1. **Slow page loads**: Check if the server has enough RAM. Node.js needs at least 512MB.
|
||||
2. **Slow API responses**: Check PocketBase query performance in the admin panel
|
||||
3. **SSE disconnects**: Ensure the reverse proxy has appropriate timeout settings (300s+)
|
||||
4. **Worker falling behind**: Increase the poll frequency or add more worker instances
|
||||
The default deployment runs one web service, one worker, and one PostgreSQL instance. Add CPU and memory before changing the topology. Multiple web services require shared session-aware infrastructure and a reverse proxy that supports long-lived SSE connections. Multiple workers coordinate through the PostgreSQL-backed job records.
|
||||
|
||||
Reference in New Issue
Block a user