Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
360 lines
15 KiB
Docker
360 lines
15 KiB
Docker
# Dockerfile.dev
|
|
# ============================================================================
|
|
# DEVELOPMENT DOCKERFILE
|
|
# ============================================================================
|
|
# This Dockerfile creates a development environment that matches production
|
|
# as closely as possible while providing the tools needed for development.
|
|
#
|
|
# Base: Ubuntu 22.04 (LTS) - matches production server
|
|
# Node: v20.x (LTS) - matches production
|
|
# Includes: PostgreSQL client, Redis CLI, build tools, Bugsink, Logstash
|
|
# ============================================================================
|
|
|
|
FROM ubuntu:22.04
|
|
|
|
# Set environment variables to non-interactive to avoid prompts during installation
|
|
ENV DEBIAN_FRONTEND=noninteractive
|
|
|
|
# ============================================================================
|
|
# Install System Dependencies
|
|
# ============================================================================
|
|
# - curl: for downloading Node.js setup script and health checks
|
|
# - git: for version control operations
|
|
# - build-essential: for compiling native Node.js modules (node-gyp)
|
|
# - python3, python3-pip, python3-venv: for Bugsink
|
|
# - postgresql-client: for psql CLI (database initialization)
|
|
# - redis-tools: for redis-cli (health checks)
|
|
# - gnupg, apt-transport-https: for Elastic APT repository (Logstash)
|
|
# - openjdk-17-jre-headless: required by Logstash
|
|
# - nginx: for proxying Vite dev server with HTTPS
|
|
# - libnss3-tools: required by mkcert for installing CA certificates
|
|
# - wget: for downloading mkcert binary
|
|
# - tzdata: timezone data required by Bugsink/Django (uses Europe/Amsterdam)
|
|
RUN apt-get update && apt-get install -y \
|
|
curl \
|
|
git \
|
|
build-essential \
|
|
python3 \
|
|
python3-pip \
|
|
python3-venv \
|
|
postgresql-client \
|
|
redis-tools \
|
|
gnupg \
|
|
apt-transport-https \
|
|
openjdk-17-jre-headless \
|
|
nginx \
|
|
libnss3-tools \
|
|
wget \
|
|
tzdata \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ============================================================================
|
|
# Install Node.js 20.x (LTS)
|
|
# ============================================================================
|
|
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
|
&& apt-get install -y nodejs
|
|
|
|
# ============================================================================
|
|
# Install PM2 Globally (ADR-014 Parity)
|
|
# ============================================================================
|
|
# Install PM2 to match production architecture. This allows dev container to
|
|
# run the same process management as production (cluster mode, workers, etc.)
|
|
RUN npm install -g pm2
|
|
|
|
# ============================================================================
|
|
# Install mkcert and Generate Self-Signed Certificates
|
|
# ============================================================================
|
|
# mkcert creates locally-trusted development certificates
|
|
# This matches production HTTPS setup but with self-signed certs for localhost
|
|
RUN wget -O /usr/local/bin/mkcert https://github.com/FiloSottile/mkcert/releases/download/v1.4.4/mkcert-v1.4.4-linux-amd64 \
|
|
&& chmod +x /usr/local/bin/mkcert
|
|
|
|
# Create certificates directory and generate localhost certificates
|
|
# ============================================================================
|
|
# IMPORTANT: Certificate includes MULTIPLE hostnames (SANs)
|
|
# ============================================================================
|
|
# The certificate is generated for 'localhost', '127.0.0.1', AND '::1' because:
|
|
#
|
|
# 1. Users may access the site via https://localhost/ OR https://127.0.0.1/
|
|
# 2. Database stores image URLs using one hostname (typically 127.0.0.1)
|
|
# 3. The seed script uses https://127.0.0.1 for image URLs (database constraint)
|
|
# 4. NGINX is configured to accept BOTH hostnames (see docker/nginx/dev.conf)
|
|
#
|
|
# Without all hostnames in the certificate's Subject Alternative Names (SANs),
|
|
# browsers would show ERR_CERT_AUTHORITY_INVALID when loading images or other
|
|
# resources that use a different hostname than the one in the address bar.
|
|
#
|
|
# The mkcert command below creates a certificate valid for all three:
|
|
# - localhost (IPv4 hostname)
|
|
# - 127.0.0.1 (IPv4 address)
|
|
# - ::1 (IPv6 loopback)
|
|
#
|
|
# See also: docker/nginx/dev.conf, docs/FLYER-URL-CONFIGURATION.md
|
|
# ============================================================================
|
|
RUN mkdir -p /app/certs \
|
|
&& cd /app/certs \
|
|
&& mkcert -install \
|
|
&& mkcert localhost 127.0.0.1 ::1 \
|
|
&& mv localhost+2.pem localhost.crt \
|
|
&& mv localhost+2-key.pem localhost.key
|
|
|
|
# ============================================================================
|
|
# Install Logstash (Elastic APT Repository)
|
|
# ============================================================================
|
|
# ADR-015: Log aggregation for Pino and Redis logs → Bugsink
|
|
RUN curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | gpg --dearmor -o /usr/share/keyrings/elastic-keyring.gpg \
|
|
&& echo "deb [signed-by=/usr/share/keyrings/elastic-keyring.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | tee /etc/apt/sources.list.d/elastic-8.x.list \
|
|
&& apt-get update \
|
|
&& apt-get install -y logstash \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
# ============================================================================
|
|
# Install Bugsink (Python Package)
|
|
# ============================================================================
|
|
# ADR-015: Self-hosted Sentry-compatible error tracking
|
|
# Create a virtual environment for Bugsink to avoid conflicts
|
|
RUN python3 -m venv /opt/bugsink \
|
|
&& /opt/bugsink/bin/pip install --upgrade pip \
|
|
&& /opt/bugsink/bin/pip install bugsink gunicorn psycopg2-binary
|
|
|
|
# Create Bugsink directories and configuration
|
|
RUN mkdir -p /var/log/bugsink /var/lib/bugsink /opt/bugsink/conf
|
|
|
|
# Create Bugsink configuration file (Django settings module)
|
|
# This file is imported by bugsink-manage via DJANGO_SETTINGS_MODULE
|
|
# Based on bugsink/conf_templates/docker.py.template but customized for our setup
|
|
RUN echo 'import os\n\
|
|
from urllib.parse import urlparse\n\
|
|
\n\
|
|
from bugsink.settings.default import *\n\
|
|
from bugsink.settings.default import DATABASES, SILENCED_SYSTEM_CHECKS\n\
|
|
from bugsink.conf_utils import deduce_allowed_hosts, deduce_script_name\n\
|
|
\n\
|
|
IS_DOCKER = True\n\
|
|
\n\
|
|
# Security settings\n\
|
|
SECRET_KEY = os.getenv("SECRET_KEY")\n\
|
|
DEBUG = os.getenv("DEBUG", "False").lower() in ("true", "1", "yes")\n\
|
|
\n\
|
|
# Silence cookie security warnings for dev (no HTTPS)\n\
|
|
SILENCED_SYSTEM_CHECKS += ["security.W012", "security.W016"]\n\
|
|
\n\
|
|
# Database configuration from DATABASE_URL environment variable\n\
|
|
if os.getenv("DATABASE_URL"):\n\
|
|
DATABASE_URL = os.getenv("DATABASE_URL")\n\
|
|
parsed = urlparse(DATABASE_URL)\n\
|
|
\n\
|
|
if parsed.scheme in ["postgres", "postgresql"]:\n\
|
|
DATABASES["default"] = {\n\
|
|
"ENGINE": "django.db.backends.postgresql",\n\
|
|
"NAME": parsed.path.lstrip("/"),\n\
|
|
"USER": parsed.username,\n\
|
|
"PASSWORD": parsed.password,\n\
|
|
"HOST": parsed.hostname,\n\
|
|
"PORT": parsed.port or "5432",\n\
|
|
}\n\
|
|
\n\
|
|
# Snappea (background task runner) settings\n\
|
|
SNAPPEA = {\n\
|
|
"TASK_ALWAYS_EAGER": False,\n\
|
|
"WORKAHOLIC": True,\n\
|
|
"NUM_WORKERS": 2,\n\
|
|
"PID_FILE": None,\n\
|
|
}\n\
|
|
DATABASES["snappea"]["NAME"] = "/tmp/snappea.sqlite3"\n\
|
|
\n\
|
|
# Site settings\n\
|
|
_PORT = os.getenv("PORT", "8000")\n\
|
|
BUGSINK = {\n\
|
|
"BASE_URL": os.getenv("BASE_URL", f"http://localhost:{_PORT}"),\n\
|
|
"SITE_TITLE": os.getenv("SITE_TITLE", "Flyer Crawler Error Tracking"),\n\
|
|
"SINGLE_USER": os.getenv("SINGLE_USER", "True").lower() in ("true", "1", "yes"),\n\
|
|
"SINGLE_TEAM": os.getenv("SINGLE_TEAM", "True").lower() in ("true", "1", "yes"),\n\
|
|
"PHONEHOME": False,\n\
|
|
}\n\
|
|
\n\
|
|
ALLOWED_HOSTS = deduce_allowed_hosts(BUGSINK["BASE_URL"])\n\
|
|
# Also allow 127.0.0.1 access (both localhost and 127.0.0.1 should work)\n\
|
|
if "127.0.0.1" not in ALLOWED_HOSTS:\n\
|
|
ALLOWED_HOSTS.append("127.0.0.1")\n\
|
|
if "localhost" not in ALLOWED_HOSTS:\n\
|
|
ALLOWED_HOSTS.append("localhost")\n\
|
|
\n\
|
|
# CSRF Trusted Origins (Django 4.0+ requires full origin for HTTPS POST requests)\n\
|
|
# This fixes "CSRF verification failed" errors when accessing Bugsink via HTTPS\n\
|
|
# Both localhost and 127.0.0.1 must be trusted to support different access patterns\n\
|
|
CSRF_TRUSTED_ORIGINS = [\n\
|
|
"https://localhost:8443",\n\
|
|
"https://127.0.0.1:8443",\n\
|
|
"http://localhost:8000",\n\
|
|
"http://127.0.0.1:8000",\n\
|
|
]\n\
|
|
\n\
|
|
# Console email backend for dev\n\
|
|
EMAIL_BACKEND = "bugsink.email_backends.QuietConsoleEmailBackend"\n\
|
|
\n\
|
|
# HTTPS proxy support (nginx reverse proxy on port 8443)\n\
|
|
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")\n\
|
|
' > /opt/bugsink/conf/bugsink_conf.py
|
|
|
|
# Create Bugsink startup script
|
|
# Uses DATABASE_URL environment variable (standard Docker approach per docs)
|
|
RUN echo '#!/bin/bash\n\
|
|
set -e\n\
|
|
\n\
|
|
# Build DATABASE_URL from individual env vars for flexibility\n\
|
|
export DATABASE_URL="postgresql://${BUGSINK_DB_USER:-bugsink}:${BUGSINK_DB_PASSWORD:-bugsink_dev_password}@${BUGSINK_DB_HOST:-postgres}:${BUGSINK_DB_PORT:-5432}/${BUGSINK_DB_NAME:-bugsink}"\n\
|
|
# SECRET_KEY is required by Bugsink/Django\n\
|
|
export SECRET_KEY="${BUGSINK_SECRET_KEY:-dev-bugsink-secret-key-minimum-50-characters-for-security}"\n\
|
|
\n\
|
|
# Create superuser if not exists (for dev convenience)\n\
|
|
if [ -n "$BUGSINK_ADMIN_EMAIL" ] && [ -n "$BUGSINK_ADMIN_PASSWORD" ]; then\n\
|
|
export CREATE_SUPERUSER="${BUGSINK_ADMIN_EMAIL}:${BUGSINK_ADMIN_PASSWORD}"\n\
|
|
fi\n\
|
|
\n\
|
|
# Wait for PostgreSQL to be ready\n\
|
|
until pg_isready -h ${BUGSINK_DB_HOST:-postgres} -p ${BUGSINK_DB_PORT:-5432} -U ${BUGSINK_DB_USER:-bugsink}; do\n\
|
|
echo "Waiting for PostgreSQL..."\n\
|
|
sleep 2\n\
|
|
done\n\
|
|
\n\
|
|
echo "PostgreSQL is ready. Starting Bugsink..."\n\
|
|
echo "DATABASE_URL: postgresql://${BUGSINK_DB_USER}:***@${BUGSINK_DB_HOST}:${BUGSINK_DB_PORT}/${BUGSINK_DB_NAME}"\n\
|
|
\n\
|
|
# Change to config directory so bugsink_conf.py can be found\n\
|
|
cd /opt/bugsink/conf\n\
|
|
\n\
|
|
# Run migrations\n\
|
|
echo "Running database migrations..."\n\
|
|
/opt/bugsink/bin/bugsink-manage migrate --noinput\n\
|
|
\n\
|
|
# Create superuser if CREATE_SUPERUSER is set (format: email:password)\n\
|
|
if [ -n "$CREATE_SUPERUSER" ]; then\n\
|
|
IFS=":" read -r ADMIN_EMAIL ADMIN_PASS <<< "$CREATE_SUPERUSER"\n\
|
|
/opt/bugsink/bin/bugsink-manage shell -c "\n\
|
|
from django.contrib.auth import get_user_model\n\
|
|
User = get_user_model()\n\
|
|
if not User.objects.filter(email='"'"'$ADMIN_EMAIL'"'"').exists():\n\
|
|
User.objects.create_superuser('"'"'$ADMIN_EMAIL'"'"', '"'"'$ADMIN_PASS'"'"')\n\
|
|
print('"'"'Superuser created'"'"')\n\
|
|
else:\n\
|
|
print('"'"'Superuser already exists'"'"')\n\
|
|
" || true\n\
|
|
fi\n\
|
|
\n\
|
|
# Start Bugsink with Gunicorn\n\
|
|
echo "Starting Gunicorn on port ${BUGSINK_PORT:-8000}..."\n\
|
|
exec /opt/bugsink/bin/gunicorn \\\n\
|
|
--bind 0.0.0.0:${BUGSINK_PORT:-8000} \\\n\
|
|
--workers ${BUGSINK_WORKERS:-2} \\\n\
|
|
--access-logfile - \\\n\
|
|
--error-logfile - \\\n\
|
|
bugsink.wsgi:application\n\
|
|
' > /usr/local/bin/start-bugsink.sh \
|
|
&& chmod +x /usr/local/bin/start-bugsink.sh
|
|
|
|
# ============================================================================
|
|
# Copy Logstash Pipeline Configuration
|
|
# ============================================================================
|
|
# ADR-015 + ADR-050: Multi-source log aggregation to Bugsink
|
|
# Configuration file includes:
|
|
# - Pino application logs (Backend API errors)
|
|
# - PostgreSQL logs (including fn_log() structured output)
|
|
# - NGINX access and error logs
|
|
# See docker/logstash/bugsink.conf for full configuration
|
|
RUN mkdir -p /etc/logstash/conf.d /app/logs
|
|
COPY docker/logstash/bugsink.conf /etc/logstash/conf.d/bugsink.conf
|
|
|
|
# Create Logstash directories
|
|
RUN mkdir -p /var/lib/logstash && chown -R logstash:logstash /var/lib/logstash
|
|
RUN mkdir -p /var/log/logstash && chown -R logstash:logstash /var/log/logstash
|
|
|
|
# Create PM2 log directory (ADR-014 Parity)
|
|
# Logs written here will be picked up by Logstash (ADR-050)
|
|
RUN mkdir -p /var/log/pm2
|
|
|
|
# ============================================================================
|
|
# Configure Nginx
|
|
# ============================================================================
|
|
# Copy development nginx configuration
|
|
COPY docker/nginx/dev.conf /etc/nginx/sites-available/default
|
|
|
|
# Configure nginx to run in foreground (required for container)
|
|
RUN echo "daemon off;" >> /etc/nginx/nginx.conf
|
|
|
|
# ============================================================================
|
|
# Set Working Directory
|
|
# ============================================================================
|
|
WORKDIR /app
|
|
|
|
# ============================================================================
|
|
# Install Node.js Dependencies
|
|
# ============================================================================
|
|
# Copy package files first for better Docker layer caching
|
|
COPY package*.json ./
|
|
|
|
# Install all dependencies (including devDependencies for development)
|
|
RUN npm install
|
|
|
|
# ============================================================================
|
|
# Environment Configuration
|
|
# ============================================================================
|
|
# Default environment variables for development
|
|
ENV NODE_ENV=development
|
|
# Increase Node.js memory limit for large builds
|
|
ENV NODE_OPTIONS='--max-old-space-size=8192'
|
|
|
|
# Bugsink defaults (ADR-015)
|
|
ENV BUGSINK_DB_HOST=postgres
|
|
ENV BUGSINK_DB_PORT=5432
|
|
ENV BUGSINK_DB_NAME=bugsink
|
|
ENV BUGSINK_DB_USER=bugsink
|
|
ENV BUGSINK_DB_PASSWORD=bugsink_dev_password
|
|
ENV BUGSINK_PORT=8000
|
|
ENV BUGSINK_BASE_URL=http://localhost:8000
|
|
ENV BUGSINK_ADMIN_EMAIL=admin@localhost
|
|
ENV BUGSINK_ADMIN_PASSWORD=admin
|
|
|
|
# ============================================================================
|
|
# Expose Ports
|
|
# ============================================================================
|
|
# 80 - HTTP redirect to HTTPS (matches production)
|
|
# 443 - Nginx HTTPS frontend proxy (Vite on 5173)
|
|
# 3001 - Express backend
|
|
# 8000 - Bugsink error tracking
|
|
EXPOSE 80 443 3001 8000
|
|
|
|
# ============================================================================
|
|
# Copy Application Code and Scripts
|
|
# ============================================================================
|
|
# Copy the scripts directory which contains the entrypoint script
|
|
COPY scripts/ /app/scripts/
|
|
|
|
# ============================================================================
|
|
# Fix Line Endings for Windows Compatibility
|
|
# ============================================================================
|
|
# Convert ALL text files from CRLF to LF (Windows to Unix)
|
|
# This ensures compatibility when building on Windows hosts
|
|
# We process: shell scripts, JS/TS files, JSON, config files, etc.
|
|
RUN find /app -type f \( \
|
|
-name "*.sh" -o \
|
|
-name "*.js" -o \
|
|
-name "*.ts" -o \
|
|
-name "*.tsx" -o \
|
|
-name "*.jsx" -o \
|
|
-name "*.json" -o \
|
|
-name "*.conf" -o \
|
|
-name "*.config" -o \
|
|
-name "*.yml" -o \
|
|
-name "*.yaml" \
|
|
\) -exec sed -i 's/\r$//' {} \; && \
|
|
find /etc/nginx -type f -name "*.conf" -exec sed -i 's/\r$//' {} \; && \
|
|
chmod +x /app/scripts/*.sh
|
|
|
|
# ============================================================================
|
|
# Default Command
|
|
# ============================================================================
|
|
# Keep container running so VS Code can attach.
|
|
# Actual commands (npm run dev, etc.) are run via devcontainer.json.
|
|
CMD ["bash"]
|