feat(docker): Add Docker deployment guide and configuration instructions

This commit is contained in:
Fatih Kadir Akın
2026-01-05 22:57:13 +03:00
parent 119564e3ba
commit b8d4854c46
11 changed files with 808 additions and 1 deletions

51
.dockerignore Normal file
View File

@@ -0,0 +1,51 @@
# Dependencies
node_modules
npm-debug.log*
# Build outputs
.next
out
dist
# Git
.git
.gitignore
# IDE
.vscode
.idea
*.swp
*.swo
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Docker
docker-compose*.yml
Dockerfile*
# Development files
.github
.claude
packages
*.md
!README.md
# Test files
coverage
.nyc_output
# OS files
.DS_Store
Thumbs.db
# Logs
logs
*.log
# Prisma
prisma/migrations/**/migration_lock.toml

62
.github/workflows/docker-publish.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Build and Publish Docker Image
on:
push:
branches:
- main
tags:
- 'v*'
pull_request:
branches:
- main
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/prompts.chat
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels)
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64

298
DOCKER.md Normal file
View File

@@ -0,0 +1,298 @@
# Docker Deployment Guide
Run prompts.chat with a single command using Docker.
## Quick Start (All-in-One Image)
The easiest way to run prompts.chat - a single container with Node.js and PostgreSQL:
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
ghcr.io/f/prompts.chat
```
Open http://localhost in your browser.
## Configuration
### Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
| `AUTH_SECRET` | Secret for authentication tokens | Auto-generated |
| `PORT` | Port to run the app on | `80` |
| `DATABASE_URL` | PostgreSQL connection string | Internal DB |
### Production Setup
For production, always set `AUTH_SECRET`:
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-e AUTH_SECRET="your-secret-key-min-32-chars-long" \
ghcr.io/f/prompts.chat
```
Generate a secure secret:
```bash
openssl rand -base64 32
```
### With OAuth Providers
Enable GitHub/Google authentication:
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-e AUTH_SECRET="your-secret-key" \
-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
```
### With AI Search (OpenAI)
Enable semantic search with OpenAI embeddings:
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-e AUTH_SECRET="your-secret-key" \
-e OPENAI_API_KEY="sk-..." \
ghcr.io/f/prompts.chat
```
## Docker Compose (Separate Containers)
For more control, use Docker Compose with separate app and database containers:
```bash
# Clone the repository
git clone https://github.com/f/awesome-chatgpt-prompts.git
cd awesome-chatgpt-prompts
# Create .env file
echo "AUTH_SECRET=$(openssl rand -base64 32)" > .env
# Start services
docker compose up -d
```
### docker-compose.yml
```yaml
services:
app:
build:
context: .
dockerfile: docker/Dockerfile.app
ports:
- "80:3000"
environment:
- DATABASE_URL=postgresql://prompts:prompts@db:5432/prompts
- AUTH_SECRET=${AUTH_SECRET}
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
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"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:
```
## 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
```
### Backup
```bash
# Backup database
docker exec prompts pg_dump -U prompts prompts > backup.sql
# Restore database
docker exec -i prompts psql -U prompts prompts < backup.sql
```
## Building Locally
Build the all-in-one image:
```bash
docker build -f docker/Dockerfile -t prompts.chat .
docker run -p 80:80 prompts.chat
```
Build the app-only image (for docker-compose):
```bash
docker build -f docker/Dockerfile.app -t prompts.chat-app .
```
## Health Check
The container includes a health check endpoint:
```bash
curl http://localhost/api/health
```
Response:
```json
{
"status": "healthy",
"timestamp": "2024-01-01T00:00:00.000Z",
"database": "connected"
}
```
## Troubleshooting
### View Logs
```bash
# All logs
docker logs prompts
# Follow logs
docker logs -f prompts
# PostgreSQL logs (inside container)
docker exec prompts cat /var/log/supervisor/postgresql.log
# Next.js logs (inside container)
docker exec prompts cat /var/log/supervisor/nextjs.log
```
### Database Access
```bash
# Connect to PostgreSQL
docker exec -it prompts psql -U prompts -d prompts
# Run SQL query
docker exec prompts psql -U prompts -d prompts -c "SELECT COUNT(*) FROM \"Prompt\""
```
### Container Shell
```bash
docker exec -it prompts bash
```
### Common Issues
**Container won't start:**
- Check logs: `docker logs prompts`
- Ensure port 80 is available: `lsof -i :80`
**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`
**Authentication issues:**
- Ensure `AUTH_SECRET` is set for production
- For OAuth, verify callback URLs are configured correctly
## Resource Requirements
Minimum:
- 1 CPU core
- 1GB RAM
- 2GB disk space
Recommended:
- 2 CPU cores
- 2GB RAM
- 10GB disk space
## Updating
```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 80:80 \
-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
server {
listen 443 ssl http2;
server_name prompts.example.com;
ssl_certificate /etc/letsencrypt/live/prompts.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/prompts.example.com/privkey.pem;
location / {
proxy_pass http://localhost:80;
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;
}
}
```
## License
MIT

46
docker-compose.yml Normal file
View File

@@ -0,0 +1,46 @@
# Docker Compose for prompts.chat
# Alternative to the all-in-one image - separates app and database
#
# Usage:
# docker compose up -d
# docker compose down
services:
app:
build:
context: .
dockerfile: docker/Dockerfile.app
ports:
- "80:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://prompts:prompts@db:5432/prompts?schema=public
- AUTH_SECRET=${AUTH_SECRET:-change-me-in-production}
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
db:
image: postgres:16-alpine
environment:
- POSTGRES_USER=prompts
- POSTGRES_PASSWORD=prompts
- POSTGRES_DB=prompts
volumes:
- postgres_data:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U prompts -d prompts"]
interval: 10s
timeout: 5s
retries: 5
volumes:
postgres_data:

91
docker/Dockerfile Normal file
View File

@@ -0,0 +1,91 @@
# prompts.chat All-in-One Docker Image
# Contains Node.js + PostgreSQL for single-container deployment
#
# Usage:
# docker run -p 80:80 ghcr.io/f/prompts.chat
# docker run -p 80:80 -e AUTH_SECRET=xxx ghcr.io/f/prompts.chat
# docker run -p 80:80 --name my-prompts ghcr.io/f/prompts.chat
FROM node:24-bookworm-slim AS builder
WORKDIR /app
# Copy package files first for better caching
COPY package.json package-lock.json ./
COPY prisma ./prisma/
# Install all dependencies (including dev for build)
RUN npm ci
# Copy application files
COPY . .
# Remove unnecessary files
RUN rm -rf .github .claude packages .git
# Generate Prisma client and build
ENV NEXT_TELEMETRY_DISABLED=1
RUN npx prisma generate && npm run build
# Production image
FROM node:24-bookworm-slim
# Labels for GitHub Container Registry
LABEL org.opencontainers.image.source="https://github.com/f/awesome-chatgpt-prompts"
LABEL org.opencontainers.image.description="prompts.chat - Self-hosted AI prompt library"
LABEL org.opencontainers.image.licenses="MIT"
# Environment variables
ENV DEBIAN_FRONTEND=noninteractive
ENV NODE_ENV=production
ENV PORT=80
ENV HOSTNAME="0.0.0.0"
ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
# Install PostgreSQL, supervisor, and utilities
RUN apt-get update && apt-get install -y --no-install-recommends \
postgresql-15 \
postgresql-contrib-15 \
supervisor \
curl \
openssl \
ca-certificates \
&& rm -rf /var/lib/apt/lists/* \
&& mkdir -p /var/run/postgresql /var/log/supervisor \
&& chown -R postgres:postgres /var/run/postgresql
WORKDIR /app
# Copy built application from builder
COPY --from=builder /app/public ./public
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/prompts.csv ./prompts.csv
COPY --from=builder /app/messages ./messages
COPY --from=builder /app/prompts.config.ts ./prompts.config.ts
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
# Copy node_modules for prisma CLI (needed for migrations)
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
COPY --from=builder /app/node_modules/prisma ./node_modules/prisma
# Copy Docker configuration files
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
# Create data directory for PostgreSQL
RUN mkdir -p /data/postgres && chown -R postgres:postgres /data/postgres
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
CMD curl -f http://localhost:80/api/health || exit 1
# Entrypoint
ENTRYPOINT ["/entrypoint.sh"]
CMD []

68
docker/Dockerfile.app Normal file
View File

@@ -0,0 +1,68 @@
# prompts.chat App-Only Dockerfile
# For use with docker-compose (separate database container)
FROM node:24-alpine AS base
# Install dependencies only when needed
FROM base AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
# Copy package files
COPY package.json package-lock.json ./
COPY prisma ./prisma/
# Install dependencies
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Remove unnecessary files
RUN rm -rf .github .claude packages docker .git
# Generate Prisma client
RUN npx prisma generate
# Build Next.js
ENV NEXT_TELEMETRY_DISABLED=1
RUN npm run build
# Production image
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
# Install netcat for health checks
RUN apk add --no-cache netcat-openbsd curl
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy built files
COPY --from=builder /app/public ./public
COPY --from=builder /app/prisma ./prisma
COPY --from=builder /app/prompts.csv ./prompts.csv
# Copy standalone build
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Copy entrypoint
COPY docker/entrypoint-app.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
ENTRYPOINT ["/entrypoint.sh"]
CMD ["node", "server.js"]

24
docker/entrypoint-app.sh Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
set -e
echo "🚀 Starting prompts.chat..."
# Wait for database to be ready
echo "⏳ Waiting for database..."
until nc -z ${DATABASE_HOST:-db} ${DATABASE_PORT:-5432}; do
sleep 1
done
echo "✅ Database is ready"
# Run migrations
echo "📦 Running database migrations..."
npx prisma migrate deploy
# Check if database needs seeding
echo "🌱 Checking database..."
# Skip seed check in entrypoint, let the app handle it
echo "✨ prompts.chat is starting on port ${PORT:-3000}"
# Execute the main command
exec "$@"

109
docker/entrypoint.sh Normal file
View File

@@ -0,0 +1,109 @@
#!/bin/bash
set -e
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ 🚀 prompts.chat - AI Prompt Library ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
# Generate AUTH_SECRET if not provided
if [ -z "$AUTH_SECRET" ]; then
export AUTH_SECRET=$(openssl rand -base64 32)
echo "⚠ AUTH_SECRET not provided, generated random secret"
echo " For production, set AUTH_SECRET environment variable"
fi
# PostgreSQL paths
PGDATA="/data/postgres"
PGBIN="/usr/lib/postgresql/15/bin"
# Initialize PostgreSQL data directory if needed
if [ ! -f "$PGDATA/PG_VERSION" ]; then
echo "▶ Initializing PostgreSQL database..."
# Initialize PostgreSQL
su postgres -c "$PGBIN/initdb -D $PGDATA"
# Configure PostgreSQL
cat >> "$PGDATA/postgresql.conf" << EOF
listen_addresses = 'localhost'
port = 5432
max_connections = 100
shared_buffers = 128MB
EOF
# Configure authentication
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
# Start PostgreSQL temporarily to create database
su postgres -c "$PGBIN/pg_ctl -D $PGDATA -l /tmp/pg.log start"
sleep 3
# Create database and user
su postgres -c "$PGBIN/createuser -s prompts 2>/dev/null" || true
su postgres -c "$PGBIN/createdb -O prompts prompts 2>/dev/null" || true
# Stop PostgreSQL (supervisor will start it)
su postgres -c "$PGBIN/pg_ctl -D $PGDATA stop"
sleep 2
echo "✓ PostgreSQL initialized"
fi
# Start supervisor (manages PostgreSQL and Next.js)
echo "▶ Starting services..."
/usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf &
SUPERVISOR_PID=$!
# Wait for PostgreSQL to be ready
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 database migrations
echo "▶ Running database migrations..."
cd /app
./node_modules/prisma/build/index.js migrate deploy
echo "✓ Migrations complete"
# Seed database if empty (check if Prompt table exists and has data)
PROMPT_COUNT=$(su postgres -c "$PGBIN/psql -h localhost -U prompts -d prompts -t -c \"SELECT COUNT(*) FROM \\\"Prompt\\\"\"" 2>/dev/null | tr -d ' ' || echo "0")
if [ "$PROMPT_COUNT" = "0" ] || [ -z "$PROMPT_COUNT" ]; then
echo "▶ Seeding database with prompts..."
# Run seed script if available
if [ -f "/app/prisma/seed.ts" ]; then
npx tsx /app/prisma/seed.ts || echo "⚠ Seeding skipped or failed"
fi
echo "✓ Database seeded"
else
echo "✓ Database already has $PROMPT_COUNT prompts"
fi
echo ""
echo "╔═══════════════════════════════════════════════════════════════╗"
echo "║ ║"
echo "║ ✅ prompts.chat is running! ║"
echo "║ ║"
echo "║ 🌐 Open http://localhost:${PORT:-80} in your browser ║"
echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
# Keep container running and forward signals
wait $SUPERVISOR_PID

28
docker/supervisord.conf Normal file
View File

@@ -0,0 +1,28 @@
[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 /app/server.js
directory=/app
user=root
autostart=true
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

View File

@@ -14,7 +14,7 @@ const nextConfig: NextConfig = {
return config;
},
// Enable standalone output for Docker
// output: "standalone",
output: "standalone",
// Experimental features
experimental: {
// Enable server actions

View File

@@ -0,0 +1,30 @@
import { NextResponse } from "next/server";
import { db } from "@/lib/db";
export const dynamic = "force-dynamic";
export async function GET() {
try {
// Check database connection
await db.$queryRaw`SELECT 1`;
return NextResponse.json(
{
status: "healthy",
timestamp: new Date().toISOString(),
database: "connected",
},
{ status: 200 }
);
} catch (error) {
return NextResponse.json(
{
status: "unhealthy",
timestamp: new Date().toISOString(),
database: "disconnected",
error: error instanceof Error ? error.message : "Unknown error",
},
{ status: 503 }
);
}
}