diff --git a/README.md b/README.md index 6b566c0..b184b6a 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,8 @@ docker compose -f docker-compose.prod.yml up -d # Access the frontend at localhost:6767 ``` +For single-container deployments, `JWT_SECRET` can be omitted and will be auto-generated and persisted in the backend volume on first start. For portability and all multi-instance deployments, set a fixed `JWT_SECRET` explicitly. + ## Docker Build [Install Docker](https://docs.docker.com/desktop/) @@ -141,7 +143,7 @@ frontend: ### Multi-Container / Kubernetes Deployments -When running multiple backend replicas (e.g., Kubernetes, Docker Swarm, or load-balanced containers), you **must** set the `CSRF_SECRET` environment variable to the same value across all instances. +When running multiple backend replicas (e.g., Kubernetes, Docker Swarm, or load-balanced containers), you **must** set both `JWT_SECRET` and `CSRF_SECRET` to the same values across all instances. ```bash # Generate a secure secret @@ -152,6 +154,7 @@ openssl rand -base64 32 # docker-compose.yml or k8s deployment backend: environment: + - JWT_SECRET=your-generated-jwt-secret-here - CSRF_SECRET=your-generated-secret-here ``` diff --git a/backend/docker-entrypoint.sh b/backend/docker-entrypoint.sh index 07889f6..9eff924 100644 --- a/backend/docker-entrypoint.sh +++ b/backend/docker-entrypoint.sh @@ -1,6 +1,30 @@ #!/bin/sh set -e +JWT_SECRET_FILE="/app/prisma/.jwt_secret" + +# Ensure JWT secret exists for production startup. +# Backward compatibility: older installs may not have JWT_SECRET configured. +if [ -z "${JWT_SECRET:-}" ]; then + echo "JWT_SECRET not provided, resolving persisted secret..." + if [ -f "${JWT_SECRET_FILE}" ]; then + JWT_SECRET="$(tr -d '\r\n' < "${JWT_SECRET_FILE}")" + fi + + if [ -z "${JWT_SECRET}" ]; then + echo "No persisted JWT secret found. Generating a new secret..." + JWT_SECRET="$(openssl rand -hex 32)" + umask 077 + printf "%s" "${JWT_SECRET}" > "${JWT_SECRET_FILE}" + fi +else + # Persist explicitly provided secret to support future restarts without env injection. + umask 077 + printf "%s" "${JWT_SECRET}" > "${JWT_SECRET_FILE}" +fi + +export JWT_SECRET + # 1. Hydrate volume if empty (Running as root) if [ ! -f "/app/prisma/schema.prisma" ]; then echo "Mount is empty. Hydrating /app/prisma..." @@ -18,6 +42,7 @@ echo "Fixing filesystem permissions..." chown -R nodejs:nodejs /app/uploads chown -R nodejs:nodejs /app/prisma chmod 755 /app/uploads +chmod 600 "${JWT_SECRET_FILE}" # Ensure database file has proper permissions if [ -f "/app/prisma/dev.db" ]; then diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index f4dddaa..268578a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -6,7 +6,9 @@ services: - DATABASE_URL=file:/app/prisma/dev.db - PORT=8000 - NODE_ENV=production - # Required for authentication: must be explicitly set to a strong secret (min 32 chars) + # Optional for single-instance deployments: + # if unset, backend auto-generates and persists one in the volume. + # Recommended to set explicitly for portability and multi-instance setups. - JWT_SECRET=${JWT_SECRET} # Required for horizontal scaling (k8s): uncomment and set to same value on all instances # - CSRF_SECRET=${CSRF_SECRET} diff --git a/docker-compose.yml b/docker-compose.yml index 20d06a1..184d77c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,7 +8,9 @@ services: - DATABASE_URL=file:/app/prisma/dev.db - PORT=8000 - NODE_ENV=production - # Required for authentication: must be explicitly set to a strong secret (min 32 chars) + # Optional for single-instance deployments: + # if unset, backend auto-generates and persists one in the volume. + # Recommended to set explicitly for portability and multi-instance setups. - JWT_SECRET=${JWT_SECRET} # Required for horizontal scaling (k8s): uncomment and set to same value on all instances # - CSRF_SECRET=${CSRF_SECRET}