Web Analytics Made Easy - Statcounter
Skip to content

Docker Deployment

Deploy Duckling using Docker for quick setup and isolation.

TL;DR - One Command Start

curl -O https://raw.githubusercontent.com/duckling-ui/duckling/main/docker-compose.prebuilt.yml && docker-compose -f docker-compose.prebuilt.yml up -d
Then open http://localhost:3000 πŸŽ‰

Prerequisites

  • Docker 20.10+
  • Docker Compose 2.0+

Quick Start

Option 1: Build Locally

# Clone the repository
git clone https://github.com/duckling-ui/duckling.git
cd duckling

# Build and start (development mode)
docker-compose up --build

# Or run in background
docker-compose up -d --build

Option 2: Use Pre-built Images

# Download docker-compose.prebuilt.yml
curl -O https://raw.githubusercontent.com/duckling-ui/duckling/main/docker-compose.prebuilt.yml

# Start with pre-built images
docker-compose -f docker-compose.prebuilt.yml up -d

Access the application at http://localhost:3000

Docker Compose Files

Duckling provides several Docker Compose configurations:

File Purpose
docker-compose.yml Development with local builds
docker-compose.prod.yml Production overrides
docker-compose.prebuilt.yml Pre-built images from registry

Development

docker-compose up --build

Production

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

Production compose sets DUCKLING_LOG_FORMAT=json and includes hardened container defaults. Optional server config:

export DUCKLING_CONFIG_FILE=/path/to/duckling-server.yaml
export DUCKLING_API_KEY=your-secret-key

Redis and worker services

Default docker-compose.yml defines optional distributed-deployment scaffolding:

Service Purpose
redis Queue backend for RQ orchestration mode
rq-worker Runs python worker.py --engine rq (standby until worker integration completes)

Set orchestration mode with DUCKLING_ENGINE_KIND=local|rq|ray. The API container runs conversions in-process by default (local). See Server Configuration and Scaling.

Ray and docling-jobkit are not in the default image; install backend/requirements-orchestration.txt when using Ray mode.

Pre-built Images

# Using default registry (ducklingui)
docker-compose -f docker-compose.prebuilt.yml up -d

# Using custom registry
DOCKER_REGISTRY=ghcr.io/yourusername docker-compose -f docker-compose.prebuilt.yml up -d

# Using specific version
VERSION=1.0.0 docker-compose -f docker-compose.prebuilt.yml up -d

Building Docker Images

Build Script

Use the provided build script for easy image building. The script automatically builds the MkDocs documentation before building Docker images:

# Build images locally (includes documentation build)
./scripts/docker-build.sh

# Build and push to Docker Hub
./scripts/docker-build.sh --push

# Build with specific version
./scripts/docker-build.sh --version 1.0.0

# Build for multiple platforms (requires buildx)
./scripts/docker-build.sh --multi-platform --push

# Push to custom registry
./scripts/docker-build.sh --push --registry ghcr.io/yourusername

# Skip documentation build (use existing site/)
./scripts/docker-build.sh --skip-docs

# Local single-arch build (recommended on laptops): CI uses amd64+arm64; building both
# locally can take many hours on some machines (QEMU) with sparse logs. Limit to one:
VERSION=$(node -p "require('./frontend/package.json').version")
./scripts/docker-build.sh --version "$VERSION" --multi-platform --platform linux/amd64 --skip-docs
# Apple Silicon without amd64 emulation: try --platform linux/arm64 instead.
# Or: DUCKLING_BUILD_PLATFORMS=linux/amd64 ./scripts/docker-build.sh --multi-platform --skip-docs

On macOS, the script is intended to run correctly with the default /bin/bash (3.2). Optional BuildKit arguments use Bash expansions that stay valid under set -u even when those flag lists are empty. In CI, pull requests run the Docker build script (publish parity) job in .github/workflows/test.yml, which checks syntax and the same optional-flag branches the Publish Docker Images workflow uses on ubuntu-latest.

When building local images with --load (no --push), Buildx does not support exporting SBOM/provenance attestations through the Docker exporter. scripts/docker-build.sh automatically disables --sbom and --provenance in that specific mode (with a warning) to avoid manifest-list export failures.

Documentation Build

The build script automatically runs mkdocs build to ensure documentation is available in the Docker containers. If MkDocs is not installed, it attempts pip install -r backend/requirements.txt before building. The backend image installs dependencies from backend/requirements.txt only.

Automatic Publishing (CI/CD)

When a pull request is merged to main, the Publish Docker Images GitHub Actions workflow runs automatically. It enforces deterministic safe versions of jaraco.context and wheel inside backend images during build (including cleanup of stale vulnerable metadata artifacts), then:

  1. Builds multi-platform images (linux/amd64, linux/arm64) once and tags for both registries
  2. Pushes to Docker Hub as {DOCKERHUB_USERNAME}/duckling-backend and {DOCKERHUB_USERNAME}/duckling-frontend
  3. Pushes the same manifests to GitHub Container Registry as ghcr.io/{owner}/duckling-backend and ghcr.io/{owner}/duckling-frontend

After the pushes complete, the workflow installs Trivy on the GitHub Actions runner, docker pulls the freshly published tags, and runs trivy image directly (instead of docker run aquasec/trivy) so scans use the same Docker Hub + GHCR logins as the build/push steps.

The backend image runs apt-get upgrade on the Debian Bookworm base before installing system packages, and the frontend production image is based on nginx:1.29-alpine3.22 and runs apk upgrade --no-cache during image build, so OS package CVE exposure stays lower during Trivy gate checks.

Before merge, PR CI runs a publish rehearsal job in .github/workflows/test.yml that builds local linux/amd64 images with --sbom/--provenance, exports them with docker save, installs Trivy CLI on the runner, and executes Trivy HIGH/CRITICAL gates using --input tar scanning, so Docker security failures are caught pre-merge without requiring daemon access from inside a Trivy container.

Images are tagged with the version from frontend/package.json and latest.

Prerelease and beta publishing (manual or tag)

Stable releases still publish on merge to main. For feature branches and betas (for example v0.0.14-doclang-beta), use one of:

Option 1 β€” Manual workflow (recommended)

  1. Open Publish Docker Images β†’ Run workflow.
  2. Select branch feature/doclang-export (or the branch that contains the updated workflow).
  3. Set ref to your tag or branch (e.g. v0.0.14-doclang-beta).
  4. Set version to the image label (e.g. 0.0.14-doclang-beta). If omitted, the workflow derives it from the tag name or frontend/package.json.
  5. Enable publish_docs only if you want docs on gh-pages / duckling-ui.org.
  6. Leave set_docs_default off for betas so latest docs stay on the stable release.

Option 2 β€” Push a prerelease tag

Pushing tags matching v*-beta*, v*-alpha*, or v*a (e.g. v0.0.14-doclang-beta) triggers the publish workflow automatically when the tag commit includes this workflow definition. Docs are not deployed unless you run the manual workflow with publish_docs enabled.

Docs only for a tagged version

Use Deploy Docs Version with version 0.0.14-doclang-beta (no v prefix). Leave set_docs_default off for betas.

Required repository secrets (Settings β†’ Secrets and variables β†’ Actions):

Secret Description
DOCKERHUB_USERNAME Docker Hub username
DOCKERHUB_TOKEN Docker Hub access token (or password)

GHCR authentication uses GITHUB_TOKEN, which GitHub Actions provides automatically.

Manual Build

# Backend (production target)
cd backend
docker build --target production -t duckling-backend:latest .

# Frontend
cd frontend
docker build --target production -t duckling-frontend:latest .

Environment Variables

Create a .env file in the project root:

# Security (required for production)
SECRET_KEY=your-very-secure-random-key-at-least-32-chars

# Flask configuration
FLASK_ENV=production
DEBUG=False

# Optional: Custom registry for pre-built images
DOCKER_REGISTRY=ducklingui
VERSION=latest

Security

Always set a strong SECRET_KEY in production. Generate one with:

python -c "import secrets; print(secrets.token_hex(32))"

Managing Containers

View Status

# Container status
docker-compose ps

# Resource usage
docker stats

View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f backend

# Last 100 lines
docker-compose logs --tail=100 backend

Stop Services

# Stop containers
docker-compose down

# Stop and remove volumes
docker-compose down -v

# Stop and remove images
docker-compose down --rmi all

Restart Services

# Restart all
docker-compose restart

# Restart specific service
docker-compose restart backend

GPU Support

For GPU-accelerated OCR with NVIDIA GPUs:

# docker-compose.gpu.yml
version: '3.8'

services:
  backend:
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    environment:
      - NVIDIA_VISIBLE_DEVICES=all

Run with:

docker-compose -f docker-compose.yml -f docker-compose.gpu.yml up

NVIDIA Container Toolkit

GPU support requires the NVIDIA Container Toolkit.

Persistent Storage

Default (Bind Mounts)

volumes:
  - ./uploads:/app/uploads
  - ./outputs:/app/outputs
services:
  backend:
    volumes:
      - duckling-uploads:/app/uploads
      - duckling-outputs:/app/outputs
      - duckling-data:/app/data

volumes:
  duckling-uploads:
  duckling-outputs:
  duckling-data:

Backup Data

# Backup volumes
docker run --rm -v duckling-outputs:/data -v $(pwd):/backup alpine tar cvf /backup/outputs-backup.tar /data

# Restore volumes
docker run --rm -v duckling-outputs:/data -v $(pwd):/backup alpine tar xvf /backup/outputs-backup.tar -C /

Health Checks

Both containers include health checks:

# Check backend health
curl http://localhost:5001/api/health
# Response: {"status": "healthy", "service": "duckling-backend"}

# Check frontend (returns HTML)
curl -I http://localhost:3000
# Response: HTTP/1.1 200 OK

Docker Compose waits for health checks:

frontend:
  depends_on:
    backend:
      condition: service_healthy

Resource Limits

Production configuration includes resource limits:

services:
  backend:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '0.5'
          memory: 1G

  frontend:
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 256M

Runtime Hardening

Production and pre-built compose configurations include hardened runtime defaults:

services:
  backend:
    read_only: true
    cap_drop: ["ALL"]
    security_opt: ["no-new-privileges:true"]
    tmpfs:
      - /tmp

  frontend:
    read_only: true
    cap_drop: ["ALL"]
    security_opt: ["no-new-privileges:true"]
    tmpfs:
      - /tmp
      - /var/cache/nginx
      - /var/run

These settings reduce container mutation and privilege escalation risk while preserving required writable runtime paths.

Networking

Services communicate over a bridge network:

networks:
  duckling-network:
    driver: bridge

The frontend proxies API requests to the backend:

Browser β†’ Frontend (nginx:3000) β†’ Backend (flask:5001)

Troubleshooting

Container Won't Start

# Check logs
docker-compose logs backend

# Check container status
docker-compose ps

# Inspect container
docker inspect duckling-backend

Port Conflicts

Change ports in docker-compose.yml:

services:
  backend:
    ports:
      - "5002:5001"  # Change external port
  frontend:
    ports:
      - "8080:3000"  # Change external port

Build Failures

# Clean build cache
docker builder prune

# Rebuild without cache
docker-compose build --no-cache

If scripts/docker-build.sh fails with docker.sock/_ping or API 500 errors, Docker Desktop/daemon is not healthy. Restart Docker, verify docker version succeeds, then rerun the build.

Memory Issues

# Check memory usage
docker stats

# Increase Docker memory limit (Docker Desktop)
# Settings β†’ Resources β†’ Memory

Network Issues

# List networks
docker network ls

# Inspect network
docker network inspect duckling_duckling-network

# Recreate network
docker-compose down
docker network prune
docker-compose up

Next Steps