Docker hardening + first-run fixes
This commit is contained in:
+10
-1
@@ -7,9 +7,15 @@
|
||||
# REQUIRED: Core Settings
|
||||
# ===========================================
|
||||
|
||||
# Your domain name (e.g., synapsis.example.com)
|
||||
# Your domain name (no scheme, no path). Example: synapsis.example.com
|
||||
DOMAIN=your-domain.com
|
||||
|
||||
# Optional: Override node domain (host only, defaults to DOMAIN)
|
||||
# NEXT_PUBLIC_NODE_DOMAIN=your-domain.com
|
||||
|
||||
# Optional: Public app URL for background jobs (auto-derived from node domain if unset)
|
||||
# NEXT_PUBLIC_APP_URL=https://your-domain.com
|
||||
|
||||
# Admin email addresses (comma-separated for multiple)
|
||||
ADMIN_EMAILS=admin@your-domain.com
|
||||
|
||||
@@ -32,5 +38,8 @@ DB_NAME=synapsis
|
||||
# Or set a specific port: PORT=3000
|
||||
PORT=auto
|
||||
|
||||
# Allow localhost domains in production (set only for local testing)
|
||||
# ALLOW_LOCALHOST=1
|
||||
|
||||
# Maximum AI bots per user (default: 5)
|
||||
# BOT_MAX_PER_USER=5
|
||||
|
||||
+3
-4
@@ -43,7 +43,7 @@ RUN npm run build
|
||||
# Stage 3: Production Runner
|
||||
# ============================================
|
||||
FROM node:22-alpine AS runner
|
||||
RUN apk add --no-cache libc6-compat openssl netcat-openbsd
|
||||
RUN apk add --no-cache libc6-compat openssl netcat-openbsd wget
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -87,10 +87,9 @@ USER nextjs
|
||||
EXPOSE 3000-3020
|
||||
|
||||
# Health check - longer start period to allow migrations to run
|
||||
# Uses PORT environment variable (set by entrypoint)
|
||||
# Reads dynamic port file when PORT=auto
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=5 \
|
||||
CMD wget -q --spider http://127.0.0.1:${PORT:-3000}/api/health || exit 1
|
||||
CMD sh -c 'PORT_FILE=/var/run/synapsis/port; PORT=3000; if [ -f "$PORT_FILE" ]; then PORT=$(cat "$PORT_FILE"); fi; case "$PORT" in ""|*[!0-9]*) PORT=3000;; esac; wget -q --spider "http://127.0.0.1:${PORT}/api/health" || exit 1'
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||
|
||||
|
||||
+8
-2
@@ -10,6 +10,7 @@ Production Docker deployment using pre-built images from GitHub Container Regist
|
||||
mkdir -p /opt/synapsis && cd /opt/synapsis
|
||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker-compose.yml
|
||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/Caddyfile
|
||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/caddy-entrypoint.sh
|
||||
curl -O https://raw.githubusercontent.com/cyph3rasi/synapsis/main/docker/.env.example
|
||||
cp .env.example .env
|
||||
nano .env # Add your domain and secrets
|
||||
@@ -39,7 +40,7 @@ newgrp docker
|
||||
|
||||
## ⚙️ Configuration
|
||||
|
||||
Edit `.env` and set these required values:
|
||||
Edit `.env` and set these required values (domain should be host only, no scheme or path):
|
||||
|
||||
| Variable | What to put |
|
||||
|----------|-------------|
|
||||
@@ -48,13 +49,18 @@ Edit `.env` and set these required values:
|
||||
| `AUTH_SECRET` | Run: `openssl rand -hex 32` |
|
||||
| `ADMIN_EMAILS` | Your email address |
|
||||
|
||||
Optional (advanced):
|
||||
- `NEXT_PUBLIC_NODE_DOMAIN` to override the node domain (defaults to `DOMAIN`)
|
||||
- `NEXT_PUBLIC_APP_URL` to override the public app URL used by background jobs (auto-derived from the node domain)
|
||||
- `ALLOW_LOCALHOST=1` to allow `localhost` in production containers for local testing
|
||||
|
||||
**Port Configuration:**
|
||||
- `PORT=auto` (default) — Automatically finds an available port between 3000-3020
|
||||
- `PORT=3000` — Use a specific port instead
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Updates
|
||||
## 🔄 Updates (migrations run automatically)
|
||||
|
||||
```bash
|
||||
cd /opt/synapsis
|
||||
|
||||
@@ -7,6 +7,24 @@ set -e
|
||||
SHARED_PORT_FILE="/var/run/synapsis/port"
|
||||
DEFAULT_PORT=3000
|
||||
|
||||
# Normalize DOMAIN (strip scheme/path) to avoid invalid Caddyfile hostnames
|
||||
sanitize_domain() {
|
||||
echo "$1" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##'
|
||||
}
|
||||
|
||||
if [ -n "$DOMAIN" ]; then
|
||||
CLEAN_DOMAIN=$(sanitize_domain "$DOMAIN")
|
||||
if [ "$CLEAN_DOMAIN" != "$DOMAIN" ]; then
|
||||
export DOMAIN="$CLEAN_DOMAIN"
|
||||
echo "🌐 DOMAIN normalized to $DOMAIN"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo "❌ DOMAIN is not set. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Wait for the port file to exist (with timeout)
|
||||
echo "⏳ Waiting for Synapsis app to announce its port..."
|
||||
TIMEOUT=60
|
||||
|
||||
@@ -57,7 +57,10 @@ services:
|
||||
AUTH_SECRET: ${AUTH_SECRET}
|
||||
|
||||
# Domain configuration
|
||||
NEXT_PUBLIC_NODE_DOMAIN: ${DOMAIN:-localhost}
|
||||
DOMAIN: ${DOMAIN:-localhost}
|
||||
NEXT_PUBLIC_NODE_DOMAIN: ${NEXT_PUBLIC_NODE_DOMAIN:-}
|
||||
NEXT_PUBLIC_APP_URL: ${NEXT_PUBLIC_APP_URL:-}
|
||||
ALLOW_LOCALHOST: ${ALLOW_LOCALHOST:-}
|
||||
|
||||
# Admin emails
|
||||
ADMIN_EMAILS: ${ADMIN_EMAILS}
|
||||
@@ -89,7 +92,7 @@ services:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
|
||||
test: ["CMD-SHELL", "PORT=$(cat /var/run/synapsis/port 2>/dev/null || echo 3000); case \"$PORT\" in ''|*[!0-9]*) PORT=3000;; esac; wget -q --spider http://127.0.0.1:${PORT}/api/health || exit 1"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
@@ -82,14 +82,79 @@ echo " Database URL: ${DATABASE_URL%%:*}://***@***"
|
||||
echo " Port: $PORT"
|
||||
echo "========================================"
|
||||
|
||||
# Ensure DATABASE_URL is set
|
||||
if [ -z "$DATABASE_URL" ]; then
|
||||
echo "❌ DATABASE_URL is not set. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Normalize domain inputs (strip scheme/path) to avoid localhost/protocol issues
|
||||
sanitize_domain() {
|
||||
echo "$1" | sed -E 's#^[a-zA-Z]+://##; s#/.*$##'
|
||||
}
|
||||
|
||||
# Normalize DOMAIN if provided
|
||||
if [ -n "$DOMAIN" ]; then
|
||||
CLEAN_DOMAIN=$(sanitize_domain "$DOMAIN")
|
||||
if [ "$CLEAN_DOMAIN" != "$DOMAIN" ]; then
|
||||
export DOMAIN="$CLEAN_DOMAIN"
|
||||
echo "🌐 DOMAIN normalized to $DOMAIN"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Normalize NEXT_PUBLIC_NODE_DOMAIN if provided
|
||||
if [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||
CLEAN_NODE_DOMAIN=$(sanitize_domain "$NEXT_PUBLIC_NODE_DOMAIN")
|
||||
if [ "$CLEAN_NODE_DOMAIN" != "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||
export NEXT_PUBLIC_NODE_DOMAIN="$CLEAN_NODE_DOMAIN"
|
||||
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN normalized to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure NEXT_PUBLIC_NODE_DOMAIN is set (fallback to DOMAIN)
|
||||
if [ -z "$NEXT_PUBLIC_NODE_DOMAIN" ] && [ -n "$DOMAIN" ]; then
|
||||
export NEXT_PUBLIC_NODE_DOMAIN="$DOMAIN"
|
||||
echo "🌐 NEXT_PUBLIC_NODE_DOMAIN set to $NEXT_PUBLIC_NODE_DOMAIN"
|
||||
fi
|
||||
|
||||
# Fail fast if running in production with localhost domain
|
||||
if [ "$NODE_ENV" = "production" ]; then
|
||||
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||
""|localhost|localhost:*|127.0.0.1|127.0.0.1:*)
|
||||
if [ -z "$ALLOW_LOCALHOST" ]; then
|
||||
echo "❌ NEXT_PUBLIC_NODE_DOMAIN is set to localhost in production."
|
||||
echo " Set DOMAIN to your public domain in .env, or set ALLOW_LOCALHOST=1 to bypass."
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Ensure NEXT_PUBLIC_APP_URL is set for background jobs
|
||||
if [ -z "$NEXT_PUBLIC_APP_URL" ] && [ -n "$NEXT_PUBLIC_NODE_DOMAIN" ]; then
|
||||
case "$NEXT_PUBLIC_NODE_DOMAIN" in
|
||||
http://*|https://*)
|
||||
NEXT_PUBLIC_APP_URL="$NEXT_PUBLIC_NODE_DOMAIN"
|
||||
;;
|
||||
localhost*|127.0.0.1*)
|
||||
NEXT_PUBLIC_APP_URL="http://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||
;;
|
||||
*)
|
||||
NEXT_PUBLIC_APP_URL="https://$NEXT_PUBLIC_NODE_DOMAIN"
|
||||
;;
|
||||
esac
|
||||
export NEXT_PUBLIC_APP_URL
|
||||
echo "🌐 NEXT_PUBLIC_APP_URL set to $NEXT_PUBLIC_APP_URL"
|
||||
fi
|
||||
|
||||
# Function to wait for database
|
||||
wait_for_db() {
|
||||
echo ""
|
||||
echo "⏳ Waiting for PostgreSQL..."
|
||||
|
||||
# Extract host and port from DATABASE_URL
|
||||
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's/.*@\([^:]*\):.*/\1/p')
|
||||
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's/.*:\([0-9]*\)\/.*/\1/p')
|
||||
DB_HOST=$(echo "$DATABASE_URL" | sed -n 's#.*@\([^/:]*\).*#\1#p')
|
||||
DB_PORT=$(echo "$DATABASE_URL" | sed -n 's#.*:\([0-9][0-9]*\)/.*#\1#p')
|
||||
DB_HOST=${DB_HOST:-postgres}
|
||||
DB_PORT=${DB_PORT:-5432}
|
||||
|
||||
@@ -119,11 +184,12 @@ run_migrations() {
|
||||
|
||||
# Run migrations using npm script
|
||||
echo " Executing: npm run db:push"
|
||||
npm run db:push 2>&1 || {
|
||||
echo "⚠️ Migration command exited with error (may be already up to date)"
|
||||
}
|
||||
|
||||
echo "✅ Migration step complete"
|
||||
if npm run db:push 2>&1; then
|
||||
echo "✅ Migration step complete"
|
||||
else
|
||||
echo "❌ Migration failed"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Wait for database
|
||||
|
||||
Reference in New Issue
Block a user