mirror of
https://github.com/f/awesome-chatgpt-prompts.git
synced 2026-02-12 15:52:47 +00:00
chore(docker): Update Docker deployment guide and workflow configurations
This commit is contained in:
7
.github/workflows/docker-publish.yml
vendored
7
.github/workflows/docker-publish.yml
vendored
@@ -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
217
DOCKER.md
@@ -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
|
||||
|
||||
@@ -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:
|
||||
@@ -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"]
|
||||
|
||||
@@ -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"]
|
||||
@@ -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
|
||||
@@ -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 "$@"
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user