From 88d89f4540e5138f1dcfc7997bfb6f0d3d64e0b9 Mon Sep 17 00:00:00 2001 From: Proudlock <270015376@geaerospace.com> Date: Mon, 7 Sep 2026 00:02:17 -0400 Subject: [PATCH] Add complete Docker deployment setup and documentation - DOCKER-SETUP.md: comprehensive Docker deployment guide - .env.docker-dev: complete Docker environment configuration - certs/: self-signed TLS certificates for HTTPS - Document first-time setup, plugin installation, database import - Troubleshooting guide for common Docker issues - Production deployment notes - Architecture explanation (gevent workers, plugin bootstrap) Addresses incomplete Docker setup - full stack deployment now documented and functional. --- DOCKER-SETUP.md | 398 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 DOCKER-SETUP.md diff --git a/DOCKER-SETUP.md b/DOCKER-SETUP.md new file mode 100644 index 00000000..afa1fce9 --- /dev/null +++ b/DOCKER-SETUP.md @@ -0,0 +1,398 @@ +# ShopDB Docker Setup + +Complete Docker deployment for development and testing. + +## Prerequisites + +- Docker Desktop installed and running +- At least 8GB RAM allocated to Docker +- Zscaler corporate certificate extracted (if behind corporate proxy) + +## Quick Start + +```bash +# 1. Clone repository +cd C:/Users/YOUR_USERNAME/Documents/github/flask-shopdb + +# 2. Extract Zscaler certificate (if needed) +powershell.exe -File C:/temp/extract-ca.ps1 +# This creates corp-root-ca.pem in repo root + +# 3. Build images +docker compose build + +# 4. Start stack +docker compose up -d + +# 5. Initialize database (first time only) +docker compose exec api flask db upgrade + +# 6. Install plugins (first time only) +docker compose exec api flask plugin apply-profile deploy/site-profile-universal.json + +# 7. Access ShopDB +https://localhost:5001 +``` + +Login: `admin` / (reset password - see below) + +## Architecture + +The Docker stack includes: + +1. **MySQL Database** (`db` service) + - Port: 3306 (internal), mapped to host based on `.env` + - Volume: `db_data` for persistence + - Health checks enabled + +2. **Flask API** (`api` service) + - Port: 5001 (HTTPS with self-signed cert) + - Built frontend served from `/app/frontend/dist` + - Gunicorn with gevent workers (4 workers, 2000 connections each) + - Volumes: + - `instance_data` - Plugin state, uploads, branding + - `./plugins` - Plugin code (read-only) + - `./scripts` - Deployment scripts (read-only) + - `./certs` - TLS certificates (read-only) + +3. **Backup Service** (optional, via `docker-compose.backup.yml`) + - One-shot container for backups + - Triggered manually or via scheduler + +## First-Time Setup + +### 1. Extract Zscaler Certificate (Corporate Proxy) + +Save this as `C:/temp/extract-ca.ps1`: + +```powershell +$request = [System.Net.HttpWebRequest]::Create('https://registry.npmjs.org/') +$request.Method = 'HEAD' +$request.Timeout = 10000 +$response = $request.GetResponse() +$root = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($request.ServicePoint.Certificate) +$builder = [System.Text.StringBuilder]::new() +[void]$builder.AppendLine('-----BEGIN CERTIFICATE-----') +[void]$builder.AppendLine([System.Convert]::ToBase64String($root.RawData, 'InsertLineBreaks')) +[void]$builder.AppendLine('-----END CERTIFICATE-----') +$certPath = 'C:\Users\270015376\Documents\github\flask-shopdb\corp-root-ca.pem' +Set-Content -Path $certPath -Value $builder.ToString() -Encoding ASCII +Write-Host "Staged corp-root-ca.pem" +``` + +Run: `powershell.exe -ExecutionPolicy Bypass -File C:/temp/extract-ca.ps1` + +### 2. Generate TLS Certificates + +```bash +cd certs +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout server.key -out server.crt -days 365 \ + -subj "//CN=localhost" +``` + +Or use the provided script: +```bash +./scripts/generate-dev-certs.sh +``` + +### 3. Configure Environment + +Copy and edit `.env`: +```bash +cp .env.docker-dev .env +# Edit if needed - defaults work for local dev +``` + +### 4. Build Images + +```bash +docker compose build +``` + +**Note:** First build takes 10-15 minutes due to: +- npm install (~5 min with Zscaler proxy) +- Frontend Vite build (~2 min) +- Python dependencies (~3 min) + +Subsequent builds use Docker layer cache and are much faster. + +### 5. Start Services + +```bash +docker compose up -d +``` + +Check status: +```bash +docker compose ps +docker compose logs -f api +``` + +### 6. Initialize Database + +```bash +# Run migrations +docker compose exec api flask db upgrade + +# Install and enable all plugins +docker compose exec api flask plugin apply-profile deploy/site-profile-universal.json +``` + +The `apply-profile` command: +- Installs all 17 plugins +- Registers them in `instance/plugins.json` +- Leaves them disabled by default +- Enables them via Settings UI as needed + +### 7. Reset Admin Password + +```bash +# Generate hash for "admin" password +docker compose exec api python -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('admin'))" + +# Update database +docker compose exec db mysql -u root -p shopdb_flask -e " +UPDATE users +SET passwordhash = 'PASTE_HASH_HERE', + failedlogins = 0, + lockeduntil = NULL +WHERE username = 'admin';" +``` + +Or use quick method (sets password to `admin`): +```bash +docker compose exec db mysql -u root -p shopdb_flask -e " +UPDATE users SET + passwordhash = 'scrypt:32768:8:1\$B4LhpPrM6qxijInK\$b99b47a0bb5fd45761a1823f401f70a3ceccf71a5d38dc433378aa07426ef8eaf7e679ff3dd7b0ce9772600f83368ac642e9419b9beaa49f87483318bae34547', + failedlogins = 0, + lockeduntil = NULL +WHERE username = 'admin';" +``` + +## Import Production Database + +```bash +# Copy SQL dump to container +docker cp latest.sql shopdb-api-1:/tmp/ + +# Import +docker compose exec db mysql -u root -p shopdb_flask < /tmp/latest.sql + +# Run migrations to bring schema up to date +docker compose exec api flask db upgrade +docker compose exec api flask plugin upgrade-all +``` + +## Common Commands + +### View Logs +```bash +docker compose logs -f api +docker compose logs -f db +``` + +### Restart Services +```bash +docker compose restart api +docker compose down && docker compose up -d +``` + +### Rebuild After Code Changes +```bash +docker compose build api +docker compose up -d api +``` + +### Access Container Shell +```bash +docker compose exec api bash +docker compose exec db bash +``` + +### Run Flask Commands +```bash +docker compose exec api flask plugin list +docker compose exec api flask db current +docker compose exec api flask routes +``` + +### Backup & Restore +```bash +# Trigger backup +docker compose -f docker-compose.backup.yml up backup + +# View backups +docker compose exec api flask plugin list +# (Accessible via Settings > Backups in UI) + +# Restore (via UI at Settings > Backups > Select backup > Restore) +``` + +## Troubleshooting + +### "npm ci failed" during build + +**Cause:** Zscaler proxy blocking npm registry. + +**Fix:** +1. Extract Zscaler certificate (see above) +2. Ensure `corp-root-ca.pem` exists in repo root +3. Rebuild: `docker compose build --no-cache` + +### "database is unhealthy" + +**Cause:** MySQL container not ready or port conflict. + +**Fix:** +```bash +# Check logs +docker compose logs db + +# Check if port 3306 is already in use +netstat -an | grep 3306 + +# If conflict, edit .env and set MYSQL_PORT=3307 +``` + +### "Access denied for user 'shopdb'" + +**Cause:** Database credentials mismatch. + +**Fix:** Check `.env` file - `MYSQL_PASSWORD` must match `DATABASE_URL`. + +### "Could not connect to database" + +**Cause:** Database service not started or wrong hostname. + +**Fix:** +```bash +# Check db is running +docker compose ps + +# API must use hostname 'db' not 'localhost' +# In docker-compose.yml: DATABASE_URL=...@db:3306/... +``` + +### "ERR_CERT_AUTHORITY_INVALID" in browser + +**Expected:** Self-signed certificate warning. + +**Fix:** Click "Advanced" > "Proceed to localhost (unsafe)" - this is normal for dev. + +Or generate proper cert: +```bash +# Add your machine's hostname to cert +openssl req -x509 -newkey rsa:2048 -nodes \ + -keyout certs/server.key -out certs/server.crt -days 365 \ + -subj "//CN=$(hostname)" \ + -addext "subjectAltName=DNS:localhost,DNS:$(hostname),IP:127.0.0.1" +``` + +### "Plugin X is already installed" but not visible + +**Cause:** Plugin installed but not enabled. + +**Fix:** +```bash +docker compose exec api flask plugin enable X +docker compose restart api +``` + +### Build is very slow + +**Expected:** First build with Zscaler proxy takes 10-15 minutes. + +**Speed up:** +- Subsequent builds use cache (much faster) +- If rebuilding often, comment out `--no-cache` flag +- Build frontend separately on host, copy dist/ into image + +## Production Deployment Notes + +For production: + +1. **Change all secrets** in `.env`: + - `SECRET_KEY` + - `JWT_SECRET_KEY` + - `MYSQL_ROOT_PASSWORD` + - `MYSQL_PASSWORD` + +2. **Use proper TLS certificates** (not self-signed): + - Place in `./certs/server.crt` and `./certs/server.key` + - Or use reverse proxy (nginx/IIS) for TLS termination + +3. **Set `FLASK_DEBUG=0`** in production + +4. **Configure backups**: + - Set up cron job: `0 2 * * * cd /path/to/shopdb && docker compose -f docker-compose.backup.yml up backup` + - Configure SMB or S3 destination in Settings > Backups + +5. **Set `SHOPDB_ENV_LABEL`** to site name (e.g., "WEST-JEFFERSON") + +6. **Review `docker-compose.yml`**: + - Adjust worker count based on load + - Configure proper `restart` policy + - Set resource limits if needed + +## Architecture Notes + +### Why HTTPS in Docker? + +Secure context APIs (clipboard, geolocation) require HTTPS. Running over HTTP disables these browser features. + +### Why Gevent Workers? + +Endpoint agents long-poll `/api/endpoints/commands/next`. With threading, 200 agents = 200 threads = resource exhaustion. Gevent uses greenlets (cheap coroutines) so 2000+ connections per worker is feasible. + +### Plugin Bootstrap + +The entrypoint (`scripts/docker-entrypoint.sh`) automatically: +1. Runs database migrations +2. Checks if `instance/plugins.json` exists +3. If not, installs all available plugins (disabled) +4. Starts gunicorn + +This ensures imported databases with plugin tables get matching plugin registry. + +### Instance Data Persistence + +The `instance_data` volume persists: +- `plugins.json` - Which plugins are enabled +- Uploaded files (floor plans, branding, photos, warranties, slides) +- Dell OAuth tokens +- Any plugin-specific state + +**Important:** `docker compose down -v` deletes volumes - use plain `docker compose down` to preserve data. + +## File Structure + +``` +flask-shopdb/ +├── docker-compose.yml # Main stack definition +├── docker-compose.backup.yml # Backup service +├── Dockerfile # Multi-stage build +├── .env # Environment config (gitignored) +├── corp-root-ca.pem # Zscaler cert (gitignored) +├── certs/ # TLS certificates (gitignored) +│ ├── server.crt +│ └── server.key +├── scripts/ +│ ├── docker-entrypoint.sh # Container startup +│ └── bootstrap-plugins.sh # Plugin auto-install +└── deploy/ + └── site-profile-universal.json # Full plugin list +``` + +## Next Steps + +After setup: + +1. Browse to https://localhost:5001 +2. Login as `admin` +3. Go to Settings > Plugins +4. Enable plugins your site needs +5. Configure settings (CORS, backup destination, etc.) +6. Import data or start fresh + +For local development with hot-reload, see `LOCAL-DEV-SETUP.md`.