Docker hardening + first-run fixes

This commit is contained in:
Christomatt
2026-02-05 02:49:34 +01:00
parent 38ddbf8bc1
commit a686db38b0
19 changed files with 346 additions and 53 deletions
+10 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
+18
View File
@@ -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
+5 -2
View File
@@ -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
+73 -7
View File
@@ -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