cmboulanger's picture
disaster-recovery deploy of pdf-tei-editor
6a49f21 verified
|
Raw
History Blame Contribute Delete
15.1 kB
# Docker Development Guide
Comprehensive guide for building, testing, and deploying Docker images for PDF TEI Editor.
## Overview
The application uses a multi-stage Dockerfile that creates optimized production images:
- **Base stage**: System dependencies and Python/Node.js setup
- **Builder stage**: Builds frontend assets and installs dependencies
- **Test stage**: Includes test dependencies and fixtures
- **Production stage**: Minimal runtime image
## Building Images
### NPM Commands
Use npm scripts for convenience (Docker/Podman auto-detected):
```bash
# Start/stop containers
npm run container:start
npm run container:start -- --tag v1.0.0 --port 8080
npm run container:stop
npm run container:stop -- --name pdf-tei-editor-v1.0.0
# Build image locally (no push)
npm run container:build -- v1.0.0
# Build without cache (force rebuild all layers)
npm run container:build:no-cache -- v1.0.0
# Build and push to registry
npm run container:push -- v1.0.0
# Push existing image (skip build)
npm run container:push -- --no-build v1.0.0
```
### Build Script
Or use the build script directly:
```bash
# Build only (no push to registry)
bin/image-build-and-push.js --build-only v1.0.0
# Build and push to Docker Hub
bin/image-build-and-push.js v1.0.0
```
### Manual Build
```bash
# Production image
docker build -t pdf-tei-editor:latest --target production .
# Test image
docker build -t pdf-tei-editor:test --target test .
# Specific version
docker build -t pdf-tei-editor:v1.0.0 --target production .
```
### Build Stages
#### Base Stage
Sets up the foundation:
- Ubuntu 24.04 base
- Python 3.12 via uv
- Node.js 22 via nvm
- System dependencies (libmagic, curl, etc.)
#### Builder Stage
Creates production assets:
- Installs Python dependencies
- Installs Node.js dependencies
- Builds frontend (bundles JavaScript, processes CSS)
- Removes development dependencies
#### Test Stage
Extends builder with test requirements:
- Includes Playwright browsers
- Copies test fixtures
- Retains development dependencies
#### Production Stage
Minimal runtime image:
- Copies only production files from builder
- Includes demo data and import script
- Sets production mode in config
- Runs as unprivileged user
## Environment Variables
### Build-Time Variables
None currently required. All configuration is runtime.
### Runtime Variables
See [Docker Deployment Guide](../user-manual/docker-deployment.md#environment-variables) for user-facing variables.
Development-specific variables:
| Variable | Description | Default |
|----------|-------------|---------|
| `DATA_ROOT` | Parent directory for files/ and db/ | `data` |
| `IMPORT_DATA_PATH` | Path to import data from | `docker/demo-data` |
| `TEST_IN_PROGRESS` | Enables test mode features | Not set |
| `FASTAPI_APPLICATION_MODE` | Application mode (testing/production) | From config |
## Entrypoint Scripts
### Production Entrypoint (`docker/entrypoint.sh`)
Handles production container initialization:
1. Sets default port (8000)
2. Creates default admin/demo users if no passwords provided
3. Applies custom login message if set
4. Updates user passwords from environment variables
5. Imports demo data if present
6. Starts production server (waitress)
### Test Entrypoint (`docker/entrypoint-test.sh`)
Handles test container initialization:
1. Sources nvm for Node.js
2. Creates data directories
3. Imports data if `IMPORT_DATA_PATH` is set
4. Copies test fixtures
5. Creates fallback test user
6. Sets testing mode in config
7. Starts test server
## Data Import System
### Import Script (`docker/import-demo-data.sh`)
Generic script for importing PDF and XML files into the database:
```bash
#!/bin/bash
# Uses IMPORT_DATA_PATH environment variable
IMPORT_PATH="${IMPORT_DATA_PATH:-docker/demo-data}"
# Validates directory exists and contains PDF/XML files
# Imports using bin/import_files.py with --recursive-collections
# Collections determined by top-level subdirectory names
# Stores files in content-addressable storage (data/files)
# Creates metadata entries in database (data/db/metadata.db)
```
**Collection Assignment:**
- Uses `--recursive-collections` flag for automatic collection naming
- Top-level subdirectories become collection names
- Organizational directories (`pdf`, `tei`, `versions`, `version`) are skipped
- Files in the root of `IMPORT_PATH` have no collection
### Usage in Containers
#### Production
Always imports demo data on startup:
```dockerfile
# Import demo data if present
if [ -f /app/docker/import-demo-data.sh ]; then
echo "Importing demo data..."
/app/docker/import-demo-data.sh
fi
```
#### Testing
Conditionally imports based on `IMPORT_DATA_PATH`:
```dockerfile
# Import data if IMPORT_DATA_PATH is set
if [ -n "$IMPORT_DATA_PATH" ] && [ -f "/app/docker/import-demo-data.sh" ]; then
echo "Importing data from $IMPORT_DATA_PATH..."
bash /app/docker/import-demo-data.sh
fi
```
### Custom Data Import
Mount custom data and set `IMPORT_DATA_PATH`:
```bash
docker run -p 8000:8000 \
-v $(pwd)/my-documents:/app/custom-data:ro \
-e IMPORT_DATA_PATH=custom-data \
pdf-tei-editor:latest
```
The script will:
1. Check if `/app/custom-data` exists
2. Search for `.pdf` and `.xml` files recursively
3. Assign collections based on top-level subdirectories
- Files in `/app/custom-data/collection1/` → "collection1" collection
- Files in `/app/custom-data/collection1/pdf/` → "collection1" collection (skips "pdf")
- Files in `/app/custom-data/` (root) → no collection
4. Store files using content-addressable hashing in `/app/data/files`
5. Create metadata entries in `/app/data/db/metadata.db`
## Testing
### Test Container
The test stage includes E2E test infrastructure:
```bash
# Build test image
docker build -t pdf-tei-editor:test --target test .
# Run with docker-compose
docker-compose -f docker-compose.test.yml up
```
### Test Configuration
`docker-compose.test.yml` settings:
```yaml
environment:
- FASTAPI_APPLICATION_MODE=testing
- TEST_IN_PROGRESS=1
- IMPORT_DATA_PATH=docker/demo-data
volumes:
- ./tests/e2e/fixtures:/app/test-data:ro
```
### Running E2E Tests Against Container
```bash
# Start test container
docker-compose -f docker-compose.test.yml up -d
# Wait for health check
docker-compose -f docker-compose.test.yml ps
# Run tests from host
npm run test:e2e
# Cleanup
docker-compose -f docker-compose.test.yml down
```
## Deployment Scripts
### Container Deployment (`bin/deploy-container.sh`)
Automated deployment script with multiple modes:
```bash
# Production deployment
sudo bin/deploy-container.sh \
--image cboulanger/pdf-tei-editor:latest \
--fqdn editor.company.com \
--type production \
--admin-password secure_password \
--data-dir /opt/pdf-data \
--config-dir /opt/pdf-config \
--db-dir /opt/pdf-db
# Demo deployment (ephemeral)
sudo bin/deploy-container.sh \
--image cboulanger/pdf-tei-editor:latest \
--fqdn demo.example.com \
--type demo \
--admin-password demo123
# Local testing
bin/deploy-container.sh \
--image pdf-tei-editor:dev \
--fqdn localhost \
--port 8080 \
--no-ssl \
--no-nginx
```
Features:
- Automatic SSL certificate management (Let's Encrypt)
- Nginx reverse proxy configuration
- Persistent volume management
- Container lifecycle management (stop old, start new)
### Cron Setup (`bin/setup-cron.sh`)
Automated demo resets:
```bash
# Nightly reset at 2 AM
bin/setup-cron.sh \
--image cboulanger/pdf-tei-editor:latest \
--fqdn demo.example.com
# Custom schedule
bin/setup-cron.sh \
--image cboulanger/pdf-tei-editor:latest \
--fqdn demo.example.com \
--time "0 3 * * *"
```
## Development Workflow
### Local Development Build and Test
```bash
# 1. Build dev image locally
bin/image-build-and-push.js --build-only dev
# 2. Test locally without SSL
bin/deploy-container.sh \
--image pdf-tei-editor:dev \
--fqdn localhost \
--port 8080 \
--no-ssl \
--no-nginx \
--admin-password admin
# 3. Access at http://localhost:8080
# Login: admin/admin
# 4. View logs
docker logs -f pdf-tei-editor-localhost
```
### Version Release
```bash
# 1. Update version in package.json
npm version 1.2.0
# 2. Build and tag
bin/image-build-and-push.js --build-only v1.2.0
# 3. Test the version
bin/deploy-container.sh \
--image pdf-tei-editor:v1.2.0 \
--fqdn localhost \
--port 8080 \
--no-ssl \
--no-nginx
# 4. Push to Docker Hub
bin/image-build-and-push.js v1.2.0
# 5. Tag as latest if stable
docker tag pdf-tei-editor:v1.2.0 cboulanger/pdf-tei-editor:latest
docker push cboulanger/pdf-tei-editor:latest
```
### Docker Hub Publishing
Requires environment variables in `.env`:
```bash
DOCKER_HUB_USERNAME=your_username
DOCKER_HUB_TOKEN=your_access_token
```
The build script automatically:
1. Logs into Docker Hub
2. Builds the image
3. Tags with version and `latest`
4. Pushes to registry
## File Structure
### Build Context
```
.
├── Dockerfile # Multi-stage build definition
├── docker/
│ ├── entrypoint.sh # Production entrypoint
│ ├── entrypoint-test.sh # Test entrypoint
│ ├── import-demo-data.sh # Data import script
│ └── demo-data/ # Demo PDF/XML files
├── bin/
│ ├── deploy-container.sh # Deployment automation
│ ├── setup-cron.sh # Cron scheduling
│ └── image-build-and-push.js # Build script
└── docker-compose.test.yml # Test composition
```
### Runtime Directories
Inside container:
```
/app/
├── .venv/ # Python virtual environment
├── app/web/ # Built frontend assets
├── fastapi_app/ # Backend API
├── bin/ # Management scripts
├── schema/ # XSD schemas
├── config/ # Configuration (mountable)
├── data/ # Data storage (mountable)
│ ├── files/ # Content-addressable file storage
│ ├── db/ # Application databases
│ │ ├── metadata.db # SQLite metadata database
│ │ ├── users.json # User accounts
│ │ └── roles.json # Role definitions
│ └── versions/ # File version history
└── docker/
├── demo-data/ # Demo files for import
└── import-demo-data.sh
```
**Configuration:**
- `DATA_ROOT` environment variable controls the parent data directory (default: `data`)
- File storage is always at `DATA_ROOT/files`
- Database is always at `DATA_ROOT/db`
- This consolidated structure simplifies mounting and backups
## Dockerfile Details
### Key Sections
```dockerfile
# Base: System setup
FROM ubuntu:24.04 as base
RUN apt-get update && apt-get install -y \
python3.12 libmagic1 curl ...
# Builder: Build frontend and install deps
FROM base as builder
RUN npm run build
RUN uv sync --frozen
# Test: Add test dependencies
FROM builder as test
RUN npx playwright install --with-deps chromium
# Production: Minimal runtime
FROM base as production
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app/app/web /app/app/web
COPY docker/demo-data /app/docker/demo-data
COPY docker/import-demo-data.sh /app/docker/import-demo-data.sh
```
### Optimization Techniques
1. **Multi-stage build**: Keeps final image small (~500MB vs ~2GB)
2. **Layer caching**: Dependencies installed before code copy
3. **Minimal runtime**: Only production files in final stage
4. **Cleanup**: Removes `.pyc`, `__pycache__`, `node_modules`
## Security
### Container Security
- Non-root user (app runs as `root` currently, TODO: add unprivileged user)
- Minimal attack surface (only required ports exposed)
- No unnecessary packages in production stage
- Environment-based secrets (not baked into image)
### Image Scanning
Recommended tools:
```bash
# Trivy
trivy image pdf-tei-editor:latest
# Docker Scout
docker scout cves pdf-tei-editor:latest
```
## Troubleshooting
### Build Failures
```bash
# Clean build (no cache)
docker build --no-cache -t pdf-tei-editor:latest --target production .
# Check build logs
docker build -t pdf-tei-editor:latest --target production . 2>&1 | tee build.log
```
### Runtime Issues
```bash
# Access container shell
docker exec -it <container_id> /bin/bash
# Check logs
docker logs -f <container_id>
# Inspect running processes
docker exec -it <container_id> ps aux
# Check disk usage
docker exec -it <container_id> df -h
```
### Common Issues
**Import script not found:**
```bash
# Verify script is executable
docker exec -it <container_id> ls -la /app/docker/import-demo-data.sh
```
**Demo data not importing:**
```bash
# Check IMPORT_DATA_PATH
docker exec -it <container_id> env | grep IMPORT
# Check data directory
docker exec -it <container_id> ls -la /app/docker/demo-data
```
**Database issues:**
```bash
# Check database directory structure
docker exec -it <container_id> ls -la /app/data/db/
# Verify database file exists
docker exec -it <container_id> stat /app/data/db/metadata.db
# Check file storage
docker exec -it <container_id> ls -la /app/data/files/
```
## Performance
### Image Size
```bash
# Check image sizes
docker images pdf-tei-editor
# Expected sizes:
# production: ~500MB
# test: ~2GB (includes Playwright browsers)
# builder: ~1.5GB (not pushed to registry)
```
### Startup Time
- Production: ~3-5 seconds
- Test (with import): ~10-15 seconds
- Test (without import): ~3-5 seconds
### Resource Usage
Recommended minimums:
- **Memory**: 512MB (1GB recommended)
- **CPU**: 1 core (2 cores recommended)
- **Disk**: 2GB for image + 1GB for data
## CI/CD Integration
### GitHub Actions Example
```yaml
name: Build and Push Docker Image
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build image
run: |
docker build -t pdf-tei-editor:${{ github.ref_name }} \
--target production .
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Push image
run: |
docker tag pdf-tei-editor:${{ github.ref_name }} \
cboulanger/pdf-tei-editor:${{ github.ref_name }}
docker push cboulanger/pdf-tei-editor:${{ github.ref_name }}
```
## Best Practices
1. **Always specify versions** - Don't use `latest` in production
2. **Use health checks** - Implement proper container health monitoring
3. **Separate concerns** - Use volumes for data, environment for config
4. **Test before deploying** - Build locally and test before pushing
5. **Monitor resources** - Set memory/CPU limits in production
6. **Regular updates** - Keep base image and dependencies updated
7. **Secure secrets** - Never commit credentials, use environment variables
8. **Log aggregation** - Send logs to external system for analysis