feat: docker compose
This commit is contained in:
+5
-1
@@ -10,6 +10,7 @@ dist
|
|||||||
# Git
|
# Git
|
||||||
.git
|
.git
|
||||||
.gitignore
|
.gitignore
|
||||||
|
.gitattributes
|
||||||
|
|
||||||
# IDE
|
# IDE
|
||||||
.vscode
|
.vscode
|
||||||
@@ -23,10 +24,11 @@ dist
|
|||||||
.env.development.local
|
.env.development.local
|
||||||
.env.test.local
|
.env.test.local
|
||||||
.env.production.local
|
.env.production.local
|
||||||
|
.env.sentry-build-plugin
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
|
compose.yml
|
||||||
docker-compose*.yml
|
docker-compose*.yml
|
||||||
Dockerfile*
|
|
||||||
|
|
||||||
# Development files
|
# Development files
|
||||||
.github
|
.github
|
||||||
@@ -38,6 +40,8 @@ packages
|
|||||||
# Test files
|
# Test files
|
||||||
coverage
|
coverage
|
||||||
.nyc_output
|
.nyc_output
|
||||||
|
vitest.config.ts
|
||||||
|
vitest.setup.ts
|
||||||
|
|
||||||
# OS files
|
# OS files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
@@ -1,45 +1,68 @@
|
|||||||
# Docker Deployment Guide
|
# Docker Deployment Guide
|
||||||
|
|
||||||
Run your own prompts.chat instance with a single command.
|
Run your own prompts.chat instance using Docker Compose.
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
git clone https://github.com/f/prompts.chat.git
|
||||||
--name prompts \
|
cd prompts.chat
|
||||||
-p 4444:3000 \
|
docker compose up -d
|
||||||
-v prompts-data:/data \
|
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**First run:** The container will clone the repository and build the app (~3-5 minutes).
|
|
||||||
**Subsequent runs:** Starts immediately using the cached build.
|
|
||||||
|
|
||||||
Open http://localhost:4444 in your browser.
|
Open http://localhost:4444 in your browser.
|
||||||
|
|
||||||
## Custom Branding
|
## Using a Pre-built Image
|
||||||
|
|
||||||
Customize your instance with environment variables:
|
Edit `compose.yml` and replace the `build` block with the published image:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
docker run -d \
|
services:
|
||||||
--name my-prompts \
|
app:
|
||||||
-p 4444:3000 \
|
# build:
|
||||||
-v prompts-data:/data \
|
# context: .
|
||||||
-e PCHAT_NAME="Acme Prompts" \
|
# dockerfile: docker/Dockerfile
|
||||||
-e PCHAT_DESCRIPTION="Our team's AI prompt library" \
|
image: ghcr.io/f/prompts.chat:latest
|
||||||
-e PCHAT_COLOR="#ff6600" \
|
|
||||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
|
||||||
-e PCHAT_LOCALES="en,es,fr" \
|
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note:** Branding is applied during the first build. To change branding later, delete the volume and re-run:
|
Then run:
|
||||||
> ```bash
|
|
||||||
> docker rm -f my-prompts
|
```bash
|
||||||
> docker volume rm prompts-data
|
docker compose up -d
|
||||||
> docker run ... # with new env vars
|
```
|
||||||
> ```
|
|
||||||
|
## Standalone (Bring Your Own Database)
|
||||||
|
|
||||||
|
If you already have a PostgreSQL instance, you can run just the app container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -f docker/Dockerfile -t prompts.chat .
|
||||||
|
docker run -d \
|
||||||
|
--name prompts \
|
||||||
|
-p 4444:3000 \
|
||||||
|
-e DATABASE_URL="postgresql://user:pass@your-db-host:5432/prompts?schema=public" \
|
||||||
|
-e AUTH_SECRET="$(openssl rand -base64 32)" \
|
||||||
|
prompts.chat
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Branding
|
||||||
|
|
||||||
|
All branding is configured via `PCHAT_*` environment variables at runtime -- no rebuild needed.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# compose.yml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
environment:
|
||||||
|
# ... existing vars ...
|
||||||
|
PCHAT_NAME: "Acme Prompts"
|
||||||
|
PCHAT_DESCRIPTION: "Our team's AI prompt library"
|
||||||
|
PCHAT_COLOR: "#ff6600"
|
||||||
|
PCHAT_AUTH_PROVIDERS: "github,google"
|
||||||
|
PCHAT_LOCALES: "en,es,fr"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then restart: `docker compose up -d`
|
||||||
|
|
||||||
## Configuration Variables
|
## Configuration Variables
|
||||||
|
|
||||||
@@ -95,103 +118,102 @@ All variables are prefixed with `PCHAT_` to avoid conflicts.
|
|||||||
|
|
||||||
| Variable | Description | Default |
|
| Variable | Description | Default |
|
||||||
|----------|-------------|---------|
|
|----------|-------------|---------|
|
||||||
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated |
|
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated (set explicitly for production) |
|
||||||
| `PORT` | Internal container port | `3000` |
|
| `DATABASE_URL` | PostgreSQL connection string | Set in compose.yml |
|
||||||
| `DATABASE_URL` | PostgreSQL connection string | Internal DB |
|
| `DIRECT_URL` | Direct PostgreSQL URL (bypasses poolers) | Same as DATABASE_URL |
|
||||||
|
| `PORT` | Host port mapping | `4444` |
|
||||||
|
|
||||||
## Production Setup
|
## Production Setup
|
||||||
|
|
||||||
For production, set `AUTH_SECRET` explicitly:
|
For production, always set `AUTH_SECRET` explicitly:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
# Generate a secret
|
||||||
--name prompts \
|
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||||
-p 4444:3000 \
|
|
||||||
-v prompts-data:/data \
|
# Start with explicit secret
|
||||||
-e AUTH_SECRET="$(openssl rand -base64 32)" \
|
docker compose up -d
|
||||||
-e PCHAT_NAME="My Company Prompts" \
|
```
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
|
Or add it to a `.env` file next to `compose.yml`:
|
||||||
|
|
||||||
|
```env
|
||||||
|
AUTH_SECRET=your-secret-key-here
|
||||||
```
|
```
|
||||||
|
|
||||||
### With OAuth Providers
|
### With OAuth Providers
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
docker run -d \
|
# compose.yml
|
||||||
--name prompts \
|
services:
|
||||||
-p 4444:3000 \
|
app:
|
||||||
-v prompts-data:/data \
|
environment:
|
||||||
-e AUTH_SECRET="your-secret-key" \
|
# ... existing vars ...
|
||||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
PCHAT_AUTH_PROVIDERS: "github,google"
|
||||||
-e AUTH_GITHUB_ID="your-github-client-id" \
|
AUTH_GITHUB_ID: "your-github-client-id"
|
||||||
-e AUTH_GITHUB_SECRET="your-github-client-secret" \
|
AUTH_GITHUB_SECRET: "your-github-client-secret"
|
||||||
-e AUTH_GOOGLE_ID="your-google-client-id" \
|
AUTH_GOOGLE_ID: "your-google-client-id"
|
||||||
-e AUTH_GOOGLE_SECRET="your-google-client-secret" \
|
AUTH_GOOGLE_SECRET: "your-google-client-secret"
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### With AI Features (OpenAI)
|
### With AI Features (OpenAI)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# compose.yml
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
environment:
|
||||||
|
# ... existing vars ...
|
||||||
|
PCHAT_FEATURE_AI_SEARCH: "true"
|
||||||
|
OPENAI_API_KEY: "sk-..."
|
||||||
|
```
|
||||||
|
|
||||||
|
## Database Seeding
|
||||||
|
|
||||||
|
Seed the database with example prompts:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker run -d \
|
docker compose exec app npx prisma db seed
|
||||||
--name prompts \
|
|
||||||
-p 4444:3000 \
|
|
||||||
-v prompts-data:/data \
|
|
||||||
-e PCHAT_FEATURE_AI_SEARCH="true" \
|
|
||||||
-e OPENAI_API_KEY="sk-..." \
|
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Custom Logo
|
## Custom Logo
|
||||||
|
|
||||||
Mount your logo file:
|
Mount your logo file into the app container:
|
||||||
|
|
||||||
```bash
|
```yaml
|
||||||
docker run -d \
|
# compose.yml
|
||||||
--name prompts \
|
services:
|
||||||
-p 4444:3000 \
|
app:
|
||||||
-v prompts-data:/data \
|
volumes:
|
||||||
-v ./my-logo.svg:/data/app/public/logo.svg \
|
- ./my-logo.svg:/app/public/logo.svg
|
||||||
-e PCHAT_NAME="My App" \
|
environment:
|
||||||
ghcr.io/f/prompts.chat
|
PCHAT_LOGO: "/logo.svg"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Data Persistence
|
## Data Persistence
|
||||||
|
|
||||||
### All-in-One Image
|
PostgreSQL data is stored in the `postgres_data` named volume and persists across container restarts, rebuilds, and image updates.
|
||||||
|
|
||||||
Data is stored in `/data` inside the container:
|
|
||||||
- `/data/postgres` - PostgreSQL database files
|
|
||||||
|
|
||||||
Mount a volume to persist data:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker run -d \
|
|
||||||
-v prompts-data:/data \
|
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backup
|
### Backup
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Backup database
|
# Backup database
|
||||||
docker exec prompts pg_dump -U prompts prompts > backup.sql
|
docker compose exec db pg_dump -U prompts prompts > backup.sql
|
||||||
|
|
||||||
# Restore database
|
# Restore database
|
||||||
docker exec -i prompts psql -U prompts prompts < backup.sql
|
docker compose exec -T db psql -U prompts prompts < backup.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
## Building Locally
|
## Building Locally
|
||||||
|
|
||||||
Build and run locally:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -f docker/Dockerfile -t prompts.chat .
|
docker compose build
|
||||||
docker run -p 4444:3000 -v prompts-data:/data prompts.chat
|
docker compose up -d
|
||||||
```
|
```
|
||||||
|
|
||||||
## Health Check
|
## Health Check
|
||||||
|
|
||||||
The container includes a health check endpoint:
|
The app container includes a health check endpoint:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
curl http://localhost:4444/api/health
|
curl http://localhost:4444/api/health
|
||||||
@@ -211,48 +233,82 @@ Response:
|
|||||||
### View Logs
|
### View Logs
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# All logs
|
# All services
|
||||||
docker logs prompts
|
docker compose logs
|
||||||
|
|
||||||
# Follow logs
|
# Follow logs
|
||||||
docker logs -f prompts
|
docker compose logs -f
|
||||||
|
|
||||||
# PostgreSQL logs (inside container)
|
# App logs only
|
||||||
docker exec prompts cat /var/log/supervisor/postgresql.log
|
docker compose logs app
|
||||||
|
|
||||||
# Next.js logs (inside container)
|
# Database logs only
|
||||||
docker exec prompts cat /var/log/supervisor/nextjs.log
|
docker compose logs db
|
||||||
```
|
```
|
||||||
|
|
||||||
### Database Access
|
### Database Access
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Connect to PostgreSQL
|
# Connect to PostgreSQL
|
||||||
docker exec -it prompts psql -U prompts -d prompts
|
docker compose exec db psql -U prompts -d prompts
|
||||||
|
|
||||||
# Run SQL query
|
# Run a query
|
||||||
docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
docker compose exec db psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
||||||
```
|
```
|
||||||
|
|
||||||
### Container Shell
|
### Container Shell
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker exec -it prompts bash
|
docker compose exec app sh
|
||||||
|
docker compose exec db bash
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
**Container won't start:**
|
**App container keeps restarting:**
|
||||||
- Check logs: `docker logs prompts`
|
- Check logs: `docker compose logs app`
|
||||||
- Ensure port 4444 is available: `lsof -i :4444`
|
- Database may not be ready yet -- the entrypoint retries for up to 60 seconds
|
||||||
|
|
||||||
**Database connection errors:**
|
**Database connection errors:**
|
||||||
- Wait for PostgreSQL to initialize (can take 30-60 seconds on first run)
|
- Verify the `db` service is healthy: `docker compose ps`
|
||||||
- Check database logs: `docker exec prompts cat /var/log/supervisor/postgresql.log`
|
- Check database logs: `docker compose logs db`
|
||||||
|
|
||||||
**Authentication issues:**
|
**Authentication issues:**
|
||||||
- Ensure `AUTH_SECRET` is set for production
|
- Set `AUTH_SECRET` explicitly for production
|
||||||
- For OAuth, verify callback URLs are configured correctly
|
- For OAuth, verify callback URLs match your domain
|
||||||
|
|
||||||
|
## Updating
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If using pre-built images
|
||||||
|
docker compose pull
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# If building locally
|
||||||
|
git pull
|
||||||
|
docker compose build
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Data persists in the `postgres_data` volume across updates.
|
||||||
|
|
||||||
|
## Migrating from the Old Single-Image Setup
|
||||||
|
|
||||||
|
If you were using the previous all-in-one Docker image:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Export your database from the old container
|
||||||
|
docker exec prompts pg_dump -U prompts prompts > backup.sql
|
||||||
|
|
||||||
|
# 2. Stop and remove the old container
|
||||||
|
docker stop prompts && docker rm prompts
|
||||||
|
|
||||||
|
# 3. Start the new compose setup
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
# 4. Import your data
|
||||||
|
docker compose exec -T db psql -U prompts prompts < backup.sql
|
||||||
|
```
|
||||||
|
|
||||||
## Resource Requirements
|
## Resource Requirements
|
||||||
|
|
||||||
@@ -266,33 +322,9 @@ Recommended:
|
|||||||
- 2GB RAM
|
- 2GB RAM
|
||||||
- 10GB disk space
|
- 10GB disk space
|
||||||
|
|
||||||
## Updating
|
## Running Behind a Reverse Proxy
|
||||||
|
|
||||||
```bash
|
### Nginx
|
||||||
# Pull latest image
|
|
||||||
docker pull ghcr.io/f/prompts.chat
|
|
||||||
|
|
||||||
# Stop and remove old container
|
|
||||||
docker stop prompts && docker rm prompts
|
|
||||||
|
|
||||||
# Start new container (data persists in volume)
|
|
||||||
docker run -d \
|
|
||||||
--name prompts \
|
|
||||||
-p 4444:3000 \
|
|
||||||
-v prompts-data:/data \
|
|
||||||
-e AUTH_SECRET="your-secret-key" \
|
|
||||||
ghcr.io/f/prompts.chat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Considerations
|
|
||||||
|
|
||||||
1. **Always set AUTH_SECRET** in production
|
|
||||||
2. **Use HTTPS** - put a reverse proxy (nginx, Caddy, Traefik) in front
|
|
||||||
3. **Limit exposed ports** - only expose what's needed
|
|
||||||
4. **Regular updates** - pull the latest image regularly
|
|
||||||
5. **Backup data** - regularly backup the `/data` volume
|
|
||||||
|
|
||||||
## Example: Running Behind Nginx
|
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
server {
|
server {
|
||||||
@@ -316,6 +348,23 @@ server {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Caddy
|
||||||
|
|
||||||
|
```
|
||||||
|
prompts.example.com {
|
||||||
|
reverse_proxy localhost:4444
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
1. **Always set AUTH_SECRET** in production
|
||||||
|
2. **Use HTTPS** -- put a reverse proxy (Nginx, Caddy, Traefik) in front
|
||||||
|
3. **Change default database password** -- update `POSTGRES_PASSWORD` in compose.yml and the connection strings
|
||||||
|
4. **Limit exposed ports** -- only expose what's needed
|
||||||
|
5. **Regular updates** -- pull the latest image regularly
|
||||||
|
6. **Backup data** -- regularly backup the database
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
# Fix Docker setup: proper multi-container architecture with Docker Compose
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
The previous Docker setup bundled PostgreSQL and the Next.js app into a single image managed by `supervisord`. This is an anti-pattern — it violates the "one container, one process" principle, clones the repo and runs the full build at runtime (3–5 min cold start), and makes scaling, updating, and debugging significantly harder.
|
||||||
|
|
||||||
|
This PR replaces it with a proper Docker Compose setup following the "Docker way":
|
||||||
|
|
||||||
|
- **`app`** — Next.js application container (multi-stage build, standalone output)
|
||||||
|
- **`db`** — Official `postgres:17-bookworm` container
|
||||||
|
|
||||||
|
Database data is persisted via a named Docker volume and survives container restarts, rebuilds, and image updates.
|
||||||
|
|
||||||
|
## Changes
|
||||||
|
|
||||||
|
### New files
|
||||||
|
- **`compose.yml`** — Docker Compose configuration with `app` and `db` services, healthchecks, and a named volume for PostgreSQL data
|
||||||
|
- **`docker/entrypoint.sh`** — Lightweight entrypoint: waits for the database, runs `prisma migrate deploy`, then starts the app
|
||||||
|
|
||||||
|
### Modified files
|
||||||
|
- **`docker/Dockerfile`** — Replaced the single-stage bootstrap image with a proper 4-stage multi-stage build:
|
||||||
|
- `base` — Shared `node:24-bookworm-slim` with system dependencies
|
||||||
|
- `deps` — Runs `npm ci` (and `prisma generate` via postinstall)
|
||||||
|
- `builder` — Runs `npm run build`, produces `.next/standalone/`
|
||||||
|
- `runner` — Minimal production image; copies standalone output, installs only Prisma CLI for migrations
|
||||||
|
- **`.dockerignore`** — Added `compose.yml`, test config files
|
||||||
|
- **`DOCKER.md`** — Complete rewrite covering the new compose-based workflow, standalone single-container usage, migration guide from the old setup, backup/restore, and production deployment tips
|
||||||
|
|
||||||
|
### Deleted files
|
||||||
|
- `docker/bootstrap.sh` — Replaced by `docker/entrypoint.sh`
|
||||||
|
- `docker/supervisord.conf` — No longer needed (one process per container)
|
||||||
|
|
||||||
|
## Key design decisions
|
||||||
|
|
||||||
|
- **PCHAT_* env vars work at runtime** — `src/lib/config/index.ts` already applies overrides via `applyEnvOverrides()`. No rebuild is needed to change branding or feature flags; just update environment variables and restart.
|
||||||
|
- **Migrations run at startup** — `prisma migrate deploy` runs in the entrypoint before the app starts, keeping deployments simple without init containers.
|
||||||
|
- **Seeding is manual** — `docker compose exec app npx prisma db seed`
|
||||||
|
- **All images are Debian-based** — `node:24-bookworm-slim` (bookworm) and `postgres:17-bookworm`
|
||||||
|
- **`.github/workflows/docker-publish.yml` needs no changes** — it already builds `./docker/Dockerfile` with context `.`
|
||||||
|
|
||||||
|
## Quick start after this PR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/f/prompts.chat.git
|
||||||
|
cd prompts.chat
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Open http://localhost:4444
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- `docker compose build` — builds successfully
|
||||||
|
- `docker compose up -d` — both services start, DB healthcheck passes before app starts
|
||||||
|
- `curl http://localhost:4444/api/health` — returns `{"status":"healthy","database":"connected"}`
|
||||||
|
- `curl http://localhost:4444/` — homepage returns 200
|
||||||
|
- Data persists across `docker compose down && docker compose up -d`
|
||||||
+44
@@ -0,0 +1,44 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:17-bookworm
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: prompts
|
||||||
|
POSTGRES_PASSWORD: prompts
|
||||||
|
POSTGRES_DB: prompts
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U prompts -d prompts"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: docker/Dockerfile
|
||||||
|
# To use a pre-built image instead of building locally, comment out
|
||||||
|
# the 'build' block above and uncomment the line below:
|
||||||
|
# image: ghcr.io/f/prompts.chat:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${PORT:-4444}:3000"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "postgresql://prompts:prompts@db:5432/prompts?schema=public"
|
||||||
|
DIRECT_URL: "postgresql://prompts:prompts@db:5432/prompts?schema=public"
|
||||||
|
AUTH_SECRET: "${AUTH_SECRET:-}"
|
||||||
|
NODE_ENV: production
|
||||||
|
# Branding (all optional — set any PCHAT_* variable to customize at runtime)
|
||||||
|
# PCHAT_NAME: "My Prompt Library"
|
||||||
|
# PCHAT_DESCRIPTION: "Our team's AI prompt collection"
|
||||||
|
# PCHAT_COLOR: "#6366f1"
|
||||||
|
# PCHAT_AUTH_PROVIDERS: "credentials"
|
||||||
|
# PCHAT_LOCALES: "en"
|
||||||
|
depends_on:
|
||||||
|
db:
|
||||||
|
condition: service_healthy
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
+73
-28
@@ -1,49 +1,94 @@
|
|||||||
# prompts.chat Bootstrap Docker Image
|
# prompts.chat Docker Image
|
||||||
# Lightweight image that clones and builds on first run
|
# Multi-stage build for a production-ready Next.js application
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage with Docker Compose:
|
||||||
# docker run -p 4444:3000 -v prompts-data:/data ghcr.io/f/prompts.chat
|
# docker compose up -d
|
||||||
# docker run -p 4444:3000 -v prompts-data:/data -e PCHAT_NAME="My App" ghcr.io/f/prompts.chat
|
#
|
||||||
|
# Usage standalone (bring your own PostgreSQL):
|
||||||
|
# docker build -f docker/Dockerfile -t prompts.chat .
|
||||||
|
# docker run -p 4444:3000 -e DATABASE_URL="postgresql://..." prompts.chat
|
||||||
|
|
||||||
FROM node:24-bookworm-slim
|
# ---- Stage 1: Base ----
|
||||||
|
FROM node:24-bookworm-slim AS base
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
openssl \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# ---- Stage 2: Dependencies ----
|
||||||
|
FROM base AS deps
|
||||||
|
|
||||||
|
# Copy dependency manifests and Prisma schema (postinstall runs prisma generate)
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY prisma/schema.prisma prisma/schema.prisma
|
||||||
|
COPY prisma.config.ts tsconfig.json ./
|
||||||
|
|
||||||
|
# Dummy DATABASE_URL for prisma generate (no actual connection is made)
|
||||||
|
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
|
||||||
|
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# ---- Stage 3: Builder ----
|
||||||
|
FROM base AS builder
|
||||||
|
|
||||||
|
COPY --from=deps /app/node_modules ./node_modules
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
|
||||||
|
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ---- Stage 4: Runner ----
|
||||||
|
FROM node:24-bookworm-slim AS runner
|
||||||
|
|
||||||
LABEL org.opencontainers.image.source="https://github.com/f/prompts.chat"
|
LABEL org.opencontainers.image.source="https://github.com/f/prompts.chat"
|
||||||
LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library"
|
LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library"
|
||||||
LABEL org.opencontainers.image.licenses="MIT"
|
LABEL org.opencontainers.image.licenses="MIT"
|
||||||
|
|
||||||
# Install PostgreSQL, git, and utilities
|
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
postgresql-15 \
|
|
||||||
postgresql-contrib-15 \
|
|
||||||
supervisor \
|
|
||||||
git \
|
|
||||||
curl \
|
|
||||||
openssl \
|
openssl \
|
||||||
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
&& mkdir -p /var/run/postgresql /var/log/supervisor \
|
|
||||||
&& chown -R postgres:postgres /var/run/postgresql
|
|
||||||
|
|
||||||
# Create directories
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
RUN mkdir -p /data/postgres /data/app && chown -R postgres:postgres /data/postgres
|
|
||||||
|
|
||||||
# Copy bootstrap scripts
|
RUN groupadd --system --gid 1001 nodejs && \
|
||||||
COPY docker/bootstrap.sh /bootstrap.sh
|
useradd --system --uid 1001 --gid nodejs nextjs
|
||||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
|
||||||
RUN chmod +x /bootstrap.sh
|
# Copy standalone build output
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/public ./public
|
||||||
|
|
||||||
|
# Copy Prisma schema, migrations, and config for runtime migrations
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/prisma ./prisma
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/prisma.config.ts ./prisma.config.ts
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json
|
||||||
|
COPY --from=builder --chown=nextjs:nodejs /app/tsconfig.json ./tsconfig.json
|
||||||
|
|
||||||
|
# Install only Prisma CLI and its dependencies for runtime migrations
|
||||||
|
RUN npm install --no-save prisma@6.19 dotenv
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
COPY --chown=nextjs:nodejs docker/entrypoint.sh ./entrypoint.sh
|
||||||
|
RUN chmod +x ./entrypoint.sh
|
||||||
|
|
||||||
# Environment defaults
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
ENV PORT=3000
|
ENV PORT=3000
|
||||||
ENV HOSTNAME="0.0.0.0"
|
ENV HOSTNAME=0.0.0.0
|
||||||
ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV DIRECT_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
|
||||||
ENV REPO_URL="https://github.com/f/prompts.chat.git"
|
|
||||||
|
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
|
||||||
CMD curl -f http://localhost:3000/api/health || exit 1
|
CMD curl -f http://localhost:3000/api/health || exit 1
|
||||||
|
|
||||||
ENTRYPOINT ["/bootstrap.sh"]
|
USER nextjs
|
||||||
|
|
||||||
|
ENTRYPOINT ["./entrypoint.sh"]
|
||||||
|
|||||||
@@ -1,181 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "║ 🚀 prompts.chat - AI Prompt Library ║"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Paths
|
|
||||||
APP_DIR="/data/app"
|
|
||||||
PGDATA="/data/postgres"
|
|
||||||
PGBIN="/usr/lib/postgresql/15/bin"
|
|
||||||
BUILD_MARKER="/data/.built"
|
|
||||||
|
|
||||||
# Generate AUTH_SECRET if not provided
|
|
||||||
if [ -z "$AUTH_SECRET" ]; then
|
|
||||||
if [ -f "/data/.auth_secret" ]; then
|
|
||||||
export AUTH_SECRET=$(cat /data/.auth_secret)
|
|
||||||
else
|
|
||||||
export AUTH_SECRET=$(openssl rand -base64 32)
|
|
||||||
echo "$AUTH_SECRET" > /data/.auth_secret
|
|
||||||
echo "⚠ AUTH_SECRET generated and saved"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Initialize PostgreSQL if needed
|
|
||||||
if [ ! -f "$PGDATA/PG_VERSION" ]; then
|
|
||||||
echo "▶ Initializing PostgreSQL..."
|
|
||||||
su postgres -c "$PGBIN/initdb -D $PGDATA"
|
|
||||||
|
|
||||||
cat >> "$PGDATA/postgresql.conf" << EOF
|
|
||||||
listen_addresses = 'localhost'
|
|
||||||
port = 5432
|
|
||||||
max_connections = 100
|
|
||||||
shared_buffers = 128MB
|
|
||||||
EOF
|
|
||||||
|
|
||||||
cat > "$PGDATA/pg_hba.conf" << EOF
|
|
||||||
local all all trust
|
|
||||||
host all all 127.0.0.1/32 trust
|
|
||||||
host all all ::1/128 trust
|
|
||||||
EOF
|
|
||||||
|
|
||||||
su postgres -c "$PGBIN/pg_ctl -D $PGDATA -l /tmp/pg.log start"
|
|
||||||
sleep 3
|
|
||||||
su postgres -c "$PGBIN/createuser -s prompts 2>/dev/null" || true
|
|
||||||
su postgres -c "$PGBIN/createdb -O prompts prompts 2>/dev/null" || true
|
|
||||||
su postgres -c "$PGBIN/pg_ctl -D $PGDATA stop"
|
|
||||||
sleep 2
|
|
||||||
echo "✓ PostgreSQL initialized"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Clone and build on first run
|
|
||||||
if [ ! -f "$BUILD_MARKER" ]; then
|
|
||||||
echo ""
|
|
||||||
echo "▶ First run detected - building prompts.chat..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Clone repository
|
|
||||||
if [ ! -d "$APP_DIR/.git" ]; then
|
|
||||||
echo "▶ Cloning repository..."
|
|
||||||
rm -rf "$APP_DIR"
|
|
||||||
git clone --depth 1 "$REPO_URL" "$APP_DIR"
|
|
||||||
echo "✓ Repository cloned"
|
|
||||||
fi
|
|
||||||
|
|
||||||
cd "$APP_DIR"
|
|
||||||
|
|
||||||
# Clean up unnecessary files
|
|
||||||
rm -rf .github .claude packages .git
|
|
||||||
|
|
||||||
# Install dependencies (including devDependencies needed for build)
|
|
||||||
echo "▶ Installing dependencies..."
|
|
||||||
NODE_ENV=development npm ci
|
|
||||||
echo "✓ Dependencies installed"
|
|
||||||
|
|
||||||
# Run docker-setup.js to generate config with branding
|
|
||||||
echo "▶ Generating configuration..."
|
|
||||||
node scripts/docker-setup.js
|
|
||||||
echo "✓ Configuration generated"
|
|
||||||
|
|
||||||
# Generate Prisma client
|
|
||||||
echo "▶ Generating Prisma client..."
|
|
||||||
npx prisma generate
|
|
||||||
echo "✓ Prisma client generated"
|
|
||||||
|
|
||||||
# Build Next.js
|
|
||||||
echo "▶ Building Next.js application (this may take a few minutes)..."
|
|
||||||
npm run build
|
|
||||||
echo "✓ Build complete"
|
|
||||||
|
|
||||||
# Copy static files for standalone mode
|
|
||||||
echo "▶ Copying static assets..."
|
|
||||||
cp -r .next/static .next/standalone/.next/
|
|
||||||
cp -r public .next/standalone/
|
|
||||||
echo "✓ Static assets copied"
|
|
||||||
|
|
||||||
# Mark as built
|
|
||||||
touch "$BUILD_MARKER"
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "✅ Build complete! Starting application..."
|
|
||||||
echo ""
|
|
||||||
else
|
|
||||||
echo "✓ Using existing build"
|
|
||||||
cd "$APP_DIR"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Start supervisord (manages PostgreSQL and Next.js)
|
|
||||||
echo "▶ Starting services..."
|
|
||||||
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf &
|
|
||||||
SUPERVISOR_PID=$!
|
|
||||||
|
|
||||||
# Wait for PostgreSQL
|
|
||||||
echo "▶ Waiting for PostgreSQL..."
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if $PGBIN/pg_isready -h localhost -p 5432 >/dev/null 2>&1; then
|
|
||||||
echo "✓ PostgreSQL is ready"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if [ $i -eq 30 ]; then
|
|
||||||
echo "✗ PostgreSQL failed to start"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Run migrations
|
|
||||||
echo "▶ Running database migrations..."
|
|
||||||
cd "$APP_DIR"
|
|
||||||
npx prisma migrate deploy
|
|
||||||
echo "✓ Migrations complete"
|
|
||||||
|
|
||||||
# Seed on first run only
|
|
||||||
SEED_MARKER="/data/.seeded"
|
|
||||||
if [ ! -f "$SEED_MARKER" ]; then
|
|
||||||
echo "▶ Seeding database..."
|
|
||||||
if npx tsx prisma/seed.ts 2>/dev/null; then
|
|
||||||
touch "$SEED_MARKER"
|
|
||||||
echo "✓ Database seeded"
|
|
||||||
else
|
|
||||||
echo "⚠ Seeding skipped"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Wait for supervisord to be ready
|
|
||||||
echo "▶ Waiting for supervisord..."
|
|
||||||
for i in $(seq 1 30); do
|
|
||||||
if kill -0 $SUPERVISOR_PID 2>/dev/null && [ -S /var/run/supervisor.sock ]; then
|
|
||||||
echo "✓ Supervisord is ready"
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
if ! kill -0 $SUPERVISOR_PID 2>/dev/null; then
|
|
||||||
echo "✗ Supervisord process died"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
if [ $i -eq 30 ]; then
|
|
||||||
echo "✗ Supervisord socket not available after 30s"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
# Start Next.js via supervisorctl
|
|
||||||
echo "▶ Starting Next.js..."
|
|
||||||
supervisorctl start nextjs
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "╔═══════════════════════════════════════════════════════════════╗"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "║ ✅ prompts.chat is running! ║"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "║ 🌐 Open http://localhost:${PORT:-80} in your browser ║"
|
|
||||||
echo "║ ║"
|
|
||||||
echo "╚═══════════════════════════════════════════════════════════════╝"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
wait $SUPERVISOR_PID
|
|
||||||
Executable
+39
@@ -0,0 +1,39 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Generate AUTH_SECRET if not provided
|
||||||
|
if [ -z "$AUTH_SECRET" ]; then
|
||||||
|
AUTH_SECRET=$(openssl rand -base64 32)
|
||||||
|
export AUTH_SECRET
|
||||||
|
echo "[prompts.chat] AUTH_SECRET not set -- generated a random value."
|
||||||
|
echo "[prompts.chat] Set AUTH_SECRET explicitly for production to persist sessions across restarts."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Wait for PostgreSQL to be ready
|
||||||
|
echo "[prompts.chat] Waiting for database..."
|
||||||
|
MAX_RETRIES=30
|
||||||
|
RETRY_COUNT=0
|
||||||
|
until node -e "
|
||||||
|
const net = require('net');
|
||||||
|
const url = new URL(process.env.DATABASE_URL);
|
||||||
|
const sock = net.createConnection({ host: url.hostname, port: url.port || 5432 });
|
||||||
|
sock.on('connect', () => { sock.destroy(); process.exit(0); });
|
||||||
|
sock.on('error', () => process.exit(1));
|
||||||
|
" 2>/dev/null; do
|
||||||
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
||||||
|
if [ "$RETRY_COUNT" -ge "$MAX_RETRIES" ]; then
|
||||||
|
echo "[prompts.chat] ERROR: Database not reachable after ${MAX_RETRIES} attempts."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "[prompts.chat] Database not ready (attempt ${RETRY_COUNT}/${MAX_RETRIES}), retrying in 2s..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
# Run database migrations
|
||||||
|
echo "[prompts.chat] Running database migrations..."
|
||||||
|
npx prisma migrate deploy
|
||||||
|
echo "[prompts.chat] Migrations applied successfully."
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
echo "[prompts.chat] Starting application on port ${PORT:-3000}..."
|
||||||
|
exec node server.js
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
[unix_http_server]
|
|
||||||
file=/var/run/supervisor.sock
|
|
||||||
chmod=0700
|
|
||||||
|
|
||||||
[supervisorctl]
|
|
||||||
serverurl=unix:///var/run/supervisor.sock
|
|
||||||
|
|
||||||
[rpcinterface:supervisor]
|
|
||||||
supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface
|
|
||||||
|
|
||||||
[supervisord]
|
|
||||||
nodaemon=true
|
|
||||||
user=root
|
|
||||||
logfile=/var/log/supervisor/supervisord.log
|
|
||||||
pidfile=/var/run/supervisord.pid
|
|
||||||
childlogdir=/var/log/supervisor
|
|
||||||
|
|
||||||
[program:postgresql]
|
|
||||||
command=/usr/lib/postgresql/15/bin/postgres -D /data/postgres
|
|
||||||
user=postgres
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
priority=10
|
|
||||||
stdout_logfile=/var/log/supervisor/postgresql.log
|
|
||||||
stderr_logfile=/var/log/supervisor/postgresql-error.log
|
|
||||||
|
|
||||||
[program:nextjs]
|
|
||||||
command=node /data/app/.next/standalone/server.js
|
|
||||||
directory=/data/app
|
|
||||||
user=root
|
|
||||||
autostart=false
|
|
||||||
autorestart=true
|
|
||||||
priority=20
|
|
||||||
startsecs=10
|
|
||||||
startretries=3
|
|
||||||
environment=NODE_ENV="production",PORT="%(ENV_PORT)s",HOSTNAME="0.0.0.0",DATABASE_URL="%(ENV_DATABASE_URL)s",AUTH_SECRET="%(ENV_AUTH_SECRET)s"
|
|
||||||
stdout_logfile=/var/log/supervisor/nextjs.log
|
|
||||||
stderr_logfile=/var/log/supervisor/nextjs-error.log
|
|
||||||
Reference in New Issue
Block a user