feat: docker compose
This commit is contained in:
+5
-1
@@ -10,6 +10,7 @@ dist
|
||||
# Git
|
||||
.git
|
||||
.gitignore
|
||||
.gitattributes
|
||||
|
||||
# IDE
|
||||
.vscode
|
||||
@@ -23,10 +24,11 @@ dist
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.sentry-build-plugin
|
||||
|
||||
# Docker
|
||||
compose.yml
|
||||
docker-compose*.yml
|
||||
Dockerfile*
|
||||
|
||||
# Development files
|
||||
.github
|
||||
@@ -38,6 +40,8 @@ packages
|
||||
# Test files
|
||||
coverage
|
||||
.nyc_output
|
||||
vitest.config.ts
|
||||
vitest.setup.ts
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
|
||||
@@ -1,45 +1,68 @@
|
||||
# Docker Deployment Guide
|
||||
|
||||
Run your own prompts.chat instance with a single command.
|
||||
Run your own prompts.chat instance using Docker Compose.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
ghcr.io/f/prompts.chat
|
||||
git clone https://github.com/f/prompts.chat.git
|
||||
cd prompts.chat
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
**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.
|
||||
|
||||
## 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
|
||||
docker run -d \
|
||||
--name my-prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e PCHAT_NAME="Acme Prompts" \
|
||||
-e PCHAT_DESCRIPTION="Our team's AI prompt library" \
|
||||
-e PCHAT_COLOR="#ff6600" \
|
||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
||||
-e PCHAT_LOCALES="en,es,fr" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
services:
|
||||
app:
|
||||
# build:
|
||||
# context: .
|
||||
# dockerfile: docker/Dockerfile
|
||||
image: ghcr.io/f/prompts.chat:latest
|
||||
```
|
||||
|
||||
> **Note:** Branding is applied during the first build. To change branding later, delete the volume and re-run:
|
||||
> ```bash
|
||||
> docker rm -f my-prompts
|
||||
> docker volume rm prompts-data
|
||||
> docker run ... # with new env vars
|
||||
> ```
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## 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
|
||||
|
||||
@@ -95,103 +118,102 @@ All variables are prefixed with `PCHAT_` to avoid conflicts.
|
||||
|
||||
| Variable | Description | Default |
|
||||
|----------|-------------|---------|
|
||||
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated |
|
||||
| `PORT` | Internal container port | `3000` |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Internal DB |
|
||||
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated (set explicitly for production) |
|
||||
| `DATABASE_URL` | PostgreSQL connection string | Set in compose.yml |
|
||||
| `DIRECT_URL` | Direct PostgreSQL URL (bypasses poolers) | Same as DATABASE_URL |
|
||||
| `PORT` | Host port mapping | `4444` |
|
||||
|
||||
## Production Setup
|
||||
|
||||
For production, set `AUTH_SECRET` explicitly:
|
||||
For production, always set `AUTH_SECRET` explicitly:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e AUTH_SECRET="$(openssl rand -base64 32)" \
|
||||
-e PCHAT_NAME="My Company Prompts" \
|
||||
ghcr.io/f/prompts.chat
|
||||
# Generate a secret
|
||||
export AUTH_SECRET=$(openssl rand -base64 32)
|
||||
|
||||
# Start with explicit secret
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Or add it to a `.env` file next to `compose.yml`:
|
||||
|
||||
```env
|
||||
AUTH_SECRET=your-secret-key-here
|
||||
```
|
||||
|
||||
### With OAuth Providers
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-e AUTH_SECRET="your-secret-key" \
|
||||
-e PCHAT_AUTH_PROVIDERS="github,google" \
|
||||
-e AUTH_GITHUB_ID="your-github-client-id" \
|
||||
-e AUTH_GITHUB_SECRET="your-github-client-secret" \
|
||||
-e AUTH_GOOGLE_ID="your-google-client-id" \
|
||||
-e AUTH_GOOGLE_SECRET="your-google-client-secret" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
environment:
|
||||
# ... existing vars ...
|
||||
PCHAT_AUTH_PROVIDERS: "github,google"
|
||||
AUTH_GITHUB_ID: "your-github-client-id"
|
||||
AUTH_GITHUB_SECRET: "your-github-client-secret"
|
||||
AUTH_GOOGLE_ID: "your-google-client-id"
|
||||
AUTH_GOOGLE_SECRET: "your-google-client-secret"
|
||||
```
|
||||
|
||||
### 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
|
||||
docker run -d \
|
||||
--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
|
||||
docker compose exec app npx prisma db seed
|
||||
```
|
||||
|
||||
## Custom Logo
|
||||
|
||||
Mount your logo file:
|
||||
Mount your logo file into the app container:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
--name prompts \
|
||||
-p 4444:3000 \
|
||||
-v prompts-data:/data \
|
||||
-v ./my-logo.svg:/data/app/public/logo.svg \
|
||||
-e PCHAT_NAME="My App" \
|
||||
ghcr.io/f/prompts.chat
|
||||
```yaml
|
||||
# compose.yml
|
||||
services:
|
||||
app:
|
||||
volumes:
|
||||
- ./my-logo.svg:/app/public/logo.svg
|
||||
environment:
|
||||
PCHAT_LOGO: "/logo.svg"
|
||||
```
|
||||
|
||||
## Data Persistence
|
||||
|
||||
### All-in-One Image
|
||||
|
||||
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
|
||||
```
|
||||
PostgreSQL data is stored in the `postgres_data` named volume and persists across container restarts, rebuilds, and image updates.
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# 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
|
||||
docker exec -i prompts psql -U prompts prompts < backup.sql
|
||||
docker compose exec -T db psql -U prompts prompts < backup.sql
|
||||
```
|
||||
|
||||
## Building Locally
|
||||
|
||||
Build and run locally:
|
||||
|
||||
```bash
|
||||
docker build -f docker/Dockerfile -t prompts.chat .
|
||||
docker run -p 4444:3000 -v prompts-data:/data prompts.chat
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Health Check
|
||||
|
||||
The container includes a health check endpoint:
|
||||
The app container includes a health check endpoint:
|
||||
|
||||
```bash
|
||||
curl http://localhost:4444/api/health
|
||||
@@ -211,48 +233,82 @@ Response:
|
||||
### View Logs
|
||||
|
||||
```bash
|
||||
# All logs
|
||||
docker logs prompts
|
||||
# All services
|
||||
docker compose logs
|
||||
|
||||
# Follow logs
|
||||
docker logs -f prompts
|
||||
docker compose logs -f
|
||||
|
||||
# PostgreSQL logs (inside container)
|
||||
docker exec prompts cat /var/log/supervisor/postgresql.log
|
||||
# App logs only
|
||||
docker compose logs app
|
||||
|
||||
# Next.js logs (inside container)
|
||||
docker exec prompts cat /var/log/supervisor/nextjs.log
|
||||
# Database logs only
|
||||
docker compose logs db
|
||||
```
|
||||
|
||||
### Database Access
|
||||
|
||||
```bash
|
||||
# Connect to PostgreSQL
|
||||
docker exec -it prompts psql -U prompts -d prompts
|
||||
docker compose exec db psql -U prompts -d prompts
|
||||
|
||||
# Run SQL query
|
||||
docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
||||
# Run a query
|
||||
docker compose exec db psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
|
||||
```
|
||||
|
||||
### Container Shell
|
||||
|
||||
```bash
|
||||
docker exec -it prompts bash
|
||||
docker compose exec app sh
|
||||
docker compose exec db bash
|
||||
```
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Container won't start:**
|
||||
- Check logs: `docker logs prompts`
|
||||
- Ensure port 4444 is available: `lsof -i :4444`
|
||||
**App container keeps restarting:**
|
||||
- Check logs: `docker compose logs app`
|
||||
- Database may not be ready yet -- the entrypoint retries for up to 60 seconds
|
||||
|
||||
**Database connection errors:**
|
||||
- Wait for PostgreSQL to initialize (can take 30-60 seconds on first run)
|
||||
- Check database logs: `docker exec prompts cat /var/log/supervisor/postgresql.log`
|
||||
- Verify the `db` service is healthy: `docker compose ps`
|
||||
- Check database logs: `docker compose logs db`
|
||||
|
||||
**Authentication issues:**
|
||||
- Ensure `AUTH_SECRET` is set for production
|
||||
- For OAuth, verify callback URLs are configured correctly
|
||||
- Set `AUTH_SECRET` explicitly for production
|
||||
- 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
|
||||
|
||||
@@ -266,33 +322,9 @@ Recommended:
|
||||
- 2GB RAM
|
||||
- 10GB disk space
|
||||
|
||||
## Updating
|
||||
## Running Behind a Reverse Proxy
|
||||
|
||||
```bash
|
||||
# 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 {
|
||||
@@ -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
|
||||
|
||||
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
|
||||
# Lightweight image that clones and builds on first run
|
||||
# prompts.chat Docker Image
|
||||
# Multi-stage build for a production-ready Next.js application
|
||||
#
|
||||
# Usage:
|
||||
# docker run -p 4444:3000 -v prompts-data:/data ghcr.io/f/prompts.chat
|
||||
# docker run -p 4444:3000 -v prompts-data:/data -e PCHAT_NAME="My App" ghcr.io/f/prompts.chat
|
||||
# Usage with Docker Compose:
|
||||
# docker compose up -d
|
||||
#
|
||||
# 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.description="prompts.chat - Self-hosted AI prompt library"
|
||||
LABEL org.opencontainers.image.licenses="MIT"
|
||||
|
||||
# Install PostgreSQL, git, and utilities
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
postgresql-15 \
|
||||
postgresql-contrib-15 \
|
||||
supervisor \
|
||||
git \
|
||||
curl \
|
||||
openssl \
|
||||
curl \
|
||||
ca-certificates \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& mkdir -p /var/run/postgresql /var/log/supervisor \
|
||||
&& chown -R postgres:postgres /var/run/postgresql
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Create directories
|
||||
WORKDIR /app
|
||||
RUN mkdir -p /data/postgres /data/app && chown -R postgres:postgres /data/postgres
|
||||
|
||||
# Copy bootstrap scripts
|
||||
COPY docker/bootstrap.sh /bootstrap.sh
|
||||
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
|
||||
RUN chmod +x /bootstrap.sh
|
||||
RUN groupadd --system --gid 1001 nodejs && \
|
||||
useradd --system --uid 1001 --gid nodejs nextjs
|
||||
|
||||
# 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 PORT=3000
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
||||
ENV DIRECT_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
|
||||
ENV REPO_URL="https://github.com/f/prompts.chat.git"
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
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
|
||||
|
||||
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