chore(docker): Update Docker deployment guide and workflow configurations

This commit is contained in:
Fatih Kadir Akın
2026-01-05 23:41:03 +03:00
parent 1d0926eac1
commit ad59ae3ebb
9 changed files with 274 additions and 462 deletions

View File

@@ -6,9 +6,6 @@ on:
- main
tags:
- 'v*'
pull_request:
branches:
- main
workflow_dispatch:
env:
@@ -30,7 +27,6 @@ jobs:
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 }}
@@ -44,7 +40,6 @@ jobs:
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}}
@@ -54,7 +49,7 @@ jobs:
with:
context: .
file: ./docker/Dockerfile
push: ${{ github.event_name != 'pull_request' }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha

217
DOCKER.md
View File

@@ -1,10 +1,8 @@
# Docker Deployment Guide
Run prompts.chat with a single command using Docker.
Run your own prompts.chat instance with a single command.
## Quick Start (All-in-One Image)
The easiest way to run prompts.chat - a single container with Node.js and PostgreSQL:
## Quick Start
```bash
docker run -d \
@@ -14,73 +12,86 @@ docker run -d \
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 in your browser.
## Whitelabel / Custom Branding
## Custom Branding
Build your own branded image with custom name, logo, and colors:
Customize your instance with environment variables:
```bash
docker build \
--build-arg BRAND_NAME="My Prompt Library" \
--build-arg BRAND_DESCRIPTION="Our team's AI prompts" \
--build-arg BRAND_COLOR="#ff6600" \
--build-arg AUTH_PROVIDERS="github,google" \
--build-arg LOCALES="en,es,fr" \
-f docker/Dockerfile \
-t my-prompts .
docker run -p 80:80 -v prompts-data:/data my-prompts
```
### Build Arguments
| Argument | Description | Default |
|----------|-------------|---------|
| `BRAND_NAME` | App name shown in UI | `My Prompt Library` |
| `BRAND_DESCRIPTION` | App description | `Collect, organize, and share AI prompts` |
| `BRAND_LOGO` | Logo path (in public/) | `/logo.svg` |
| `BRAND_LOGO_DARK` | Dark mode logo | Same as `BRAND_LOGO` |
| `BRAND_FAVICON` | Favicon path | `/logo.svg` |
| `BRAND_COLOR` | Primary color (hex) | `#6366f1` |
| `THEME_RADIUS` | Border radius: `none\|sm\|md\|lg` | `sm` |
| `THEME_VARIANT` | UI style: `default\|flat\|brutal` | `default` |
| `THEME_DENSITY` | Spacing: `compact\|default\|comfortable` | `default` |
| `AUTH_PROVIDERS` | Auth providers (comma-separated) | `credentials` |
| `ALLOW_REGISTRATION` | Allow public signup | `true` |
| `LOCALES` | Supported locales (comma-separated) | `en` |
| `DEFAULT_LOCALE` | Default locale | `en` |
| `FEATURE_PRIVATE_PROMPTS` | Enable private prompts | `true` |
| `FEATURE_CHANGE_REQUESTS` | Enable versioning | `true` |
| `FEATURE_CATEGORIES` | Enable categories | `true` |
| `FEATURE_TAGS` | Enable tags | `true` |
| `FEATURE_COMMENTS` | Enable comments | `true` |
| `FEATURE_AI_SEARCH` | Enable AI search | `false` |
| `FEATURE_AI_GENERATION` | Enable AI generation | `false` |
| `FEATURE_MCP` | Enable MCP features | `false` |
### Adding Custom Logo
1. Create your logo file (SVG recommended)
2. Mount it when running:
```bash
docker run -p 80:80 \
-v ./my-logo.svg:/app/public/logo.svg \
docker run -d \
--name my-prompts \
-p 80:80 \
-v prompts-data:/data \
my-prompts
-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
```
Or include it in your own Dockerfile:
> **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
> ```
```dockerfile
FROM ghcr.io/f/prompts.chat
COPY my-logo.svg /app/public/logo.svg
```
## Configuration Variables
## Configuration
All variables are prefixed with `PCHAT_` to avoid conflicts.
### Environment Variables
#### Branding (`branding.*` in prompts.config.ts)
| Env Variable | Config Path | Description | Default |
|--------------|-------------|-------------|---------|
| `PCHAT_NAME` | `branding.name` | App name shown in UI | `My Prompt Library` |
| `PCHAT_DESCRIPTION` | `branding.description` | App description | `Collect, organize...` |
| `PCHAT_LOGO` | `branding.logo` | Logo path (in public/) | `/logo.svg` |
| `PCHAT_LOGO_DARK` | `branding.logoDark` | Dark mode logo | Same as `PCHAT_LOGO` |
| `PCHAT_FAVICON` | `branding.favicon` | Favicon path | `/logo.svg` |
#### Theme (`theme.*` in prompts.config.ts)
| Env Variable | Config Path | Description | Default |
|--------------|-------------|-------------|---------|
| `PCHAT_COLOR` | `theme.colors.primary` | Primary color (hex) | `#6366f1` |
| `PCHAT_THEME_RADIUS` | `theme.radius` | Border radius: `none\|sm\|md\|lg` | `sm` |
| `PCHAT_THEME_VARIANT` | `theme.variant` | UI style: `default\|flat\|brutal` | `default` |
| `PCHAT_THEME_DENSITY` | `theme.density` | Spacing: `compact\|default\|comfortable` | `default` |
#### Authentication (`auth.*` in prompts.config.ts)
| Env Variable | Config Path | Description | Default |
|--------------|-------------|-------------|---------|
| `PCHAT_AUTH_PROVIDERS` | `auth.providers` | Providers: `github,google,credentials` | `credentials` |
| `PCHAT_ALLOW_REGISTRATION` | `auth.allowRegistration` | Allow public signup | `true` |
#### Internationalization (`i18n.*` in prompts.config.ts)
| Env Variable | Config Path | Description | Default |
|--------------|-------------|-------------|---------|
| `PCHAT_LOCALES` | `i18n.locales` | Supported locales (comma-separated) | `en` |
| `PCHAT_DEFAULT_LOCALE` | `i18n.defaultLocale` | Default locale | `en` |
#### Features (`features.*` in prompts.config.ts)
| Env Variable | Config Path | Description | Default |
|--------------|-------------|-------------|---------|
| `PCHAT_FEATURE_PRIVATE_PROMPTS` | `features.privatePrompts` | Enable private prompts | `true` |
| `PCHAT_FEATURE_CHANGE_REQUESTS` | `features.changeRequests` | Enable versioning | `true` |
| `PCHAT_FEATURE_CATEGORIES` | `features.categories` | Enable categories | `true` |
| `PCHAT_FEATURE_TAGS` | `features.tags` | Enable tags | `true` |
| `PCHAT_FEATURE_COMMENTS` | `features.comments` | Enable comments | `true` |
| `PCHAT_FEATURE_AI_SEARCH` | `features.aiSearch` | Enable AI search | `false` |
| `PCHAT_FEATURE_AI_GENERATION` | `features.aiGeneration` | Enable AI generation | `false` |
| `PCHAT_FEATURE_MCP` | `features.mcp` | Enable MCP features | `false` |
## System Environment Variables
| Variable | Description | Default |
|----------|-------------|---------|
@@ -88,34 +99,29 @@ COPY my-logo.svg /app/public/logo.svg
| `PORT` | Port to run the app on | `80` |
| `DATABASE_URL` | PostgreSQL connection string | Internal DB |
### Production Setup
## Production Setup
For production, always set `AUTH_SECRET`:
For production, set `AUTH_SECRET` explicitly:
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-e AUTH_SECRET="your-secret-key-min-32-chars-long" \
-e AUTH_SECRET="$(openssl rand -base64 32)" \
-e PCHAT_NAME="My Company Prompts" \
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 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" \
@@ -123,69 +129,30 @@ docker run -d \
ghcr.io/f/prompts.chat
```
### With AI Search (OpenAI)
Enable semantic search with OpenAI embeddings:
### With AI Features (OpenAI)
```bash
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-e AUTH_SECRET="your-secret-key" \
-e PCHAT_FEATURE_AI_SEARCH="true" \
-e OPENAI_API_KEY="sk-..." \
ghcr.io/f/prompts.chat
```
## Docker Compose (Separate Containers)
## Custom Logo
For more control, use Docker Compose with separate app and database containers:
Mount your logo file:
```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:
docker run -d \
--name prompts \
-p 80:80 \
-v prompts-data:/data \
-v ./my-logo.svg:/data/app/public/logo.svg \
-e PCHAT_NAME="My App" \
ghcr.io/f/prompts.chat
```
## Data Persistence
@@ -215,17 +182,11 @@ docker exec -i prompts psql -U prompts prompts < backup.sql
## Building Locally
Build the all-in-one image:
Build and run locally:
```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 .
docker run -p 80:80 -v prompts-data:/data prompts.chat
```
## Health Check

View File

@@ -1,46 +0,0 @@
# 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:

View File

@@ -1,107 +1,22 @@
# prompts.chat All-in-One Docker Image
# Contains Node.js + PostgreSQL for single-container deployment
# prompts.chat Bootstrap Docker Image
# Lightweight image that clones and builds on first run
#
# Usage:
# docker build -t my-prompts .
# docker build --build-arg BRAND_NAME="My App" --build-arg BRAND_COLOR="#ff0000" -t my-prompts .
# docker run -p 80:80 my-prompts
#
# Build Arguments (all optional):
# BRAND_NAME, BRAND_DESCRIPTION, BRAND_LOGO, BRAND_COLOR
# AUTH_PROVIDERS (comma-separated: github,google,credentials)
# LOCALES (comma-separated: en,es,fr)
# docker run -p 80:80 -v prompts-data:/data ghcr.io/f/prompts.chat
# docker run -p 80:80 -v prompts-data:/data -e PCHAT_NAME="My App" ghcr.io/f/prompts.chat
FROM node:24-bookworm-slim AS builder
# Build arguments for whitelabel branding
ARG BRAND_NAME="My Prompt Library"
ARG BRAND_DESCRIPTION="Collect, organize, and share AI prompts"
ARG BRAND_LOGO="/logo.svg"
ARG BRAND_LOGO_DARK=""
ARG BRAND_FAVICON="/logo.svg"
ARG BRAND_COLOR="#6366f1"
ARG THEME_RADIUS="sm"
ARG THEME_VARIANT="default"
ARG THEME_DENSITY="default"
ARG AUTH_PROVIDERS="credentials"
ARG ALLOW_REGISTRATION="true"
ARG LOCALES="en"
ARG DEFAULT_LOCALE="en"
ARG FEATURE_PRIVATE_PROMPTS="true"
ARG FEATURE_CHANGE_REQUESTS="true"
ARG FEATURE_CATEGORIES="true"
ARG FEATURE_TAGS="true"
ARG FEATURE_COMMENTS="true"
ARG FEATURE_AI_SEARCH="false"
ARG FEATURE_AI_GENERATION="false"
ARG FEATURE_MCP="false"
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
# Run docker-setup.js to generate prompts.config.ts with branding
ENV BRAND_NAME=${BRAND_NAME}
ENV BRAND_DESCRIPTION=${BRAND_DESCRIPTION}
ENV BRAND_LOGO=${BRAND_LOGO}
ENV BRAND_LOGO_DARK=${BRAND_LOGO_DARK}
ENV BRAND_FAVICON=${BRAND_FAVICON}
ENV BRAND_COLOR=${BRAND_COLOR}
ENV THEME_RADIUS=${THEME_RADIUS}
ENV THEME_VARIANT=${THEME_VARIANT}
ENV THEME_DENSITY=${THEME_DENSITY}
ENV AUTH_PROVIDERS=${AUTH_PROVIDERS}
ENV ALLOW_REGISTRATION=${ALLOW_REGISTRATION}
ENV LOCALES=${LOCALES}
ENV DEFAULT_LOCALE=${DEFAULT_LOCALE}
ENV FEATURE_PRIVATE_PROMPTS=${FEATURE_PRIVATE_PROMPTS}
ENV FEATURE_CHANGE_REQUESTS=${FEATURE_CHANGE_REQUESTS}
ENV FEATURE_CATEGORIES=${FEATURE_CATEGORIES}
ENV FEATURE_TAGS=${FEATURE_TAGS}
ENV FEATURE_COMMENTS=${FEATURE_COMMENTS}
ENV FEATURE_AI_SEARCH=${FEATURE_AI_SEARCH}
ENV FEATURE_AI_GENERATION=${FEATURE_AI_GENERATION}
ENV FEATURE_MCP=${FEATURE_MCP}
RUN node scripts/docker-setup.js
# Generate Prisma client and build
# DATABASE_URL is needed at build time for Prisma, but not used
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
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
# 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 \
ca-certificates \
@@ -109,38 +24,25 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
&& mkdir -p /var/run/postgresql /var/log/supervisor \
&& chown -R postgres:postgres /var/run/postgresql
# Create directories
WORKDIR /app
RUN mkdir -p /data/postgres /data/app && chown -R postgres:postgres /data/postgres
# 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 bootstrap scripts
COPY docker/bootstrap.sh /bootstrap.sh
COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
RUN chmod +x /bootstrap.sh
# Create data directory for PostgreSQL
RUN mkdir -p /data/postgres && chown -R postgres:postgres /data/postgres
# Environment defaults
ENV NODE_ENV=production
ENV PORT=80
ENV HOSTNAME="0.0.0.0"
ENV DATABASE_URL="postgresql://prompts:prompts@localhost:5432/prompts?schema=public"
ENV REPO_URL="https://github.com/f/awesome-chatgpt-prompts.git"
# Expose port
EXPOSE 80
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=90s --retries=3 \
HEALTHCHECK --interval=30s --timeout=10s --start-period=180s --retries=3 \
CMD curl -f http://localhost:80/api/health || exit 1
# Entrypoint
ENTRYPOINT ["/entrypoint.sh"]
CMD []
ENTRYPOINT ["/bootstrap.sh"]

View File

@@ -1,101 +0,0 @@
# prompts.chat App-Only Dockerfile
# For use with docker-compose (separate database container)
#
# Build with branding:
# docker build --build-arg BRAND_NAME="My App" -f docker/Dockerfile.app -t my-prompts .
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
# Build arguments for whitelabel branding
ARG BRAND_NAME="My Prompt Library"
ARG BRAND_DESCRIPTION="Collect, organize, and share AI prompts"
ARG BRAND_LOGO="/logo.svg"
ARG BRAND_LOGO_DARK=""
ARG BRAND_FAVICON="/logo.svg"
ARG BRAND_COLOR="#6366f1"
ARG THEME_RADIUS="sm"
ARG THEME_VARIANT="default"
ARG AUTH_PROVIDERS="credentials"
ARG LOCALES="en"
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Remove unnecessary files
RUN rm -rf .github .claude packages docker .git
# Run docker-setup.js to generate prompts.config.ts with branding
ENV BRAND_NAME=${BRAND_NAME} \
BRAND_DESCRIPTION=${BRAND_DESCRIPTION} \
BRAND_LOGO=${BRAND_LOGO} \
BRAND_LOGO_DARK=${BRAND_LOGO_DARK} \
BRAND_FAVICON=${BRAND_FAVICON} \
BRAND_COLOR=${BRAND_COLOR} \
THEME_RADIUS=${THEME_RADIUS} \
THEME_VARIANT=${THEME_VARIANT} \
AUTH_PROVIDERS=${AUTH_PROVIDERS} \
LOCALES=${LOCALES}
RUN node scripts/docker-setup.js
# DATABASE_URL is needed at build time for Prisma, but not used
ENV NEXT_TELEMETRY_DISABLED=1
ENV DATABASE_URL="postgresql://dummy:dummy@localhost:5432/dummy"
# Generate Prisma client
RUN npx prisma generate
# Build Next.js
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"]

View File

@@ -9,25 +9,28 @@ 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
# Paths
APP_DIR="/data/app"
PGDATA="/data/postgres"
PGBIN="/usr/lib/postgresql/15/bin"
BUILD_MARKER="/data/.built"
# Initialize PostgreSQL data directory if needed
# 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 database..."
# Initialize PostgreSQL
echo "▶ Initializing PostgreSQL..."
su postgres -c "$PGBIN/initdb -D $PGDATA"
# Configure PostgreSQL
cat >> "$PGDATA/postgresql.conf" << EOF
listen_addresses = 'localhost'
port = 5432
@@ -35,34 +38,77 @@ 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
# 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
echo "▶ Installing dependencies..."
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"
# Mark as built
touch "$BUILD_MARKER"
echo ""
echo "✅ Build complete! Starting application..."
echo ""
else
echo "✓ Using existing build"
cd "$APP_DIR"
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
# 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
@@ -76,23 +122,17 @@ for i in $(seq 1 30); do
sleep 1
done
# Run database migrations
# Run migrations
echo "▶ Running database migrations..."
cd /app
./node_modules/prisma/build/index.js migrate deploy
npx prisma migrate deploy
echo "✓ Migrations complete"
# Seed database if empty (check if Prompt table exists and has data)
# Seed if empty
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"
echo "▶ Seeding database..."
npx tsx prisma/seed.ts 2>/dev/null || echo "⚠ Seeding skipped"
echo "✓ Database ready"
fi
echo ""
@@ -105,5 +145,4 @@ echo "║ ║"
echo "╚═══════════════════════════════════════════════════════════════╝"
echo ""
# Keep container running and forward signals
wait $SUPERVISOR_PID

View File

@@ -1,24 +0,0 @@
#!/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 "$@"

View File

@@ -15,8 +15,8 @@ stdout_logfile=/var/log/supervisor/postgresql.log
stderr_logfile=/var/log/supervisor/postgresql-error.log
[program:nextjs]
command=node /app/server.js
directory=/app
command=node /data/app/.next/standalone/server.js
directory=/data/app
user=root
autostart=true
autorestart=true

View File

@@ -83,17 +83,100 @@ export function defineConfig(config: PromptsConfig): PromptsConfig {
// Load the user's config
let cachedConfig: PromptsConfig | null = null;
/**
* Apply runtime environment variable overrides to config.
* This allows customization via Docker env vars without rebuilding.
*
* All env vars are prefixed with PCHAT_ to avoid conflicts.
*
* Supported env vars:
* PCHAT_NAME, PCHAT_DESCRIPTION, PCHAT_LOGO, PCHAT_LOGO_DARK, PCHAT_FAVICON, PCHAT_COLOR
* PCHAT_THEME_RADIUS (none|sm|md|lg), PCHAT_THEME_VARIANT (default|flat|brutal), PCHAT_THEME_DENSITY
* PCHAT_AUTH_PROVIDERS (comma-separated), PCHAT_ALLOW_REGISTRATION (true|false)
* PCHAT_LOCALES (comma-separated), PCHAT_DEFAULT_LOCALE
* PCHAT_FEATURE_* (true|false for each feature)
*/
function applyEnvOverrides(config: PromptsConfig): PromptsConfig {
const env = process.env;
// Helper functions
const envBool = (key: string, fallback: boolean): boolean => {
const val = env[key];
if (val === undefined) return fallback;
return val.toLowerCase() === 'true' || val === '1';
};
const envArray = (key: string, fallback: string[]): string[] => {
const val = env[key];
if (!val) return fallback;
return val.split(',').map(s => s.trim()).filter(Boolean);
};
return {
branding: {
name: env.PCHAT_NAME || config.branding.name,
description: env.PCHAT_DESCRIPTION || config.branding.description,
logo: env.PCHAT_LOGO || config.branding.logo,
logoDark: env.PCHAT_LOGO_DARK || env.PCHAT_LOGO || config.branding.logoDark,
favicon: env.PCHAT_FAVICON || config.branding.favicon,
appStoreUrl: config.branding.appStoreUrl,
chromeExtensionUrl: config.branding.chromeExtensionUrl,
},
theme: {
radius: (env.PCHAT_THEME_RADIUS as ThemeConfig['radius']) || config.theme.radius,
variant: (env.PCHAT_THEME_VARIANT as ThemeConfig['variant']) || config.theme.variant,
density: (env.PCHAT_THEME_DENSITY as ThemeConfig['density']) || config.theme.density,
colors: {
primary: env.PCHAT_COLOR || config.theme.colors.primary,
secondary: config.theme.colors.secondary,
accent: config.theme.colors.accent,
},
},
auth: {
providers: env.PCHAT_AUTH_PROVIDERS
? envArray('PCHAT_AUTH_PROVIDERS', config.auth.providers || ['credentials'])
: config.auth.providers,
allowRegistration: env.PCHAT_ALLOW_REGISTRATION !== undefined
? envBool('PCHAT_ALLOW_REGISTRATION', config.auth.allowRegistration)
: config.auth.allowRegistration,
},
i18n: {
locales: env.PCHAT_LOCALES
? envArray('PCHAT_LOCALES', config.i18n.locales)
: config.i18n.locales,
defaultLocale: env.PCHAT_DEFAULT_LOCALE || config.i18n.defaultLocale,
},
features: {
privatePrompts: envBool('PCHAT_FEATURE_PRIVATE_PROMPTS', config.features.privatePrompts),
changeRequests: envBool('PCHAT_FEATURE_CHANGE_REQUESTS', config.features.changeRequests),
categories: envBool('PCHAT_FEATURE_CATEGORIES', config.features.categories),
tags: envBool('PCHAT_FEATURE_TAGS', config.features.tags),
aiSearch: envBool('PCHAT_FEATURE_AI_SEARCH', config.features.aiSearch ?? false),
aiGeneration: envBool('PCHAT_FEATURE_AI_GENERATION', config.features.aiGeneration ?? false),
mcp: envBool('PCHAT_FEATURE_MCP', config.features.mcp ?? false),
comments: envBool('PCHAT_FEATURE_COMMENTS', config.features.comments ?? true),
},
homepage: env.PCHAT_NAME ? {
// If custom branding via env, use clone branding mode
useCloneBranding: true,
achievements: { enabled: false },
sponsors: { enabled: false, items: [] },
} : config.homepage,
};
}
export async function getConfig(): Promise<PromptsConfig> {
if (cachedConfig) return cachedConfig;
let baseConfig: PromptsConfig;
try {
// Dynamic import of user config
const userConfig = await import("@/../prompts.config");
cachedConfig = userConfig.default;
return cachedConfig;
baseConfig = userConfig.default;
} catch {
// Fallback to default config
cachedConfig = {
baseConfig = {
branding: {
name: "prompts.chat",
logo: "/logo.svg",
@@ -127,8 +210,11 @@ export async function getConfig(): Promise<PromptsConfig> {
comments: true,
},
};
return cachedConfig;
}
// Apply runtime environment variable overrides
cachedConfig = applyEnvOverrides(baseConfig);
return cachedConfig;
}
// Sync version for client components (must be initialized first)