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.
This commit is contained in:
398
DOCKER-SETUP.md
Normal file
398
DOCKER-SETUP.md
Normal file
@@ -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`.
|
||||
Reference in New Issue
Block a user