Files
shopdb-flask/DOCKER-SETUP.md
Proudlock d40f1a6a92 fix: replace unicode characters and fix finalize-restore endpoint
Style check fixes:
- Replace unicode box-drawing characters with ASCII (|-- instead of ├──)
- Replace emoji status markers with [OK]/[FAIL]/[WARN] tags
- Replace arrows (→ with ->, ↔ with <->) and em-dashes (— with --)
- Add venv-quick to excluded directories in check script
- Exclude darkwing report files (findings.sarif, report.md)

Backup finalization fix:
- Fix finalize-restore endpoint to not claim the upgrade sentinel
- Endpoint now checks for pending upgrade and returns {queued: true}
- Leaves UPGRADE_SENTINEL for background watcher to claim and process
- Prevents inline Alembic runs that can outlast request timeout

Claude Code context:
- Add .claude/ directory with INDEX.md and QUICK-REFERENCE.md
- Add CLAUDE.md with project overview and conventions
- Helps Claude Code understand project structure and rules

Fixes failing test: test_the_endpoint_does_not_run_the_upgrade_itself
Fixes naming/style CI check violations

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-09-07 03:48:11 -04:00

10 KiB

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

# 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:

$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

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:

./scripts/generate-dev-certs.sh

3. Configure Environment

Copy and edit .env:

cp .env.docker-dev .env
# Edit if needed - defaults work for local dev

4. Build Images

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

docker compose up -d

Check status:

docker compose ps
docker compose logs -f api

6. Initialize Database

# 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

# 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):

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

# 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

docker compose logs -f api
docker compose logs -f db

Restart Services

docker compose restart api
docker compose down && docker compose up -d

Rebuild After Code Changes

docker compose build api
docker compose up -d api

Access Container Shell

docker compose exec api bash
docker compose exec db bash

Run Flask Commands

docker compose exec api flask plugin list
docker compose exec api flask db current
docker compose exec api flask routes

Backup & Restore

# 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:

# 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:

# 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:

# 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:

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.