4.1 KiB
Docker Deployment
The production image builds the Vue frontend with Node 22, installs the Python runtime dependencies into the official Python 3.14 slim image, and runs Uvicorn as UID/GID 10001 with one worker. Application data is stored in the translator-data named volume at /app/data.
Prerequisites
- Docker Engine with the Compose plugin
config/llms.jsoncontaining the production LLM endpoints and credentials- Bootstrap administrator environment variables for a new Docker volume
The configuration files are not copied into the image or build context. Compose mounts them read-only. Docker uses the empty config/users.docker.json, so the repository's documented local development account is never imported into a production volume.
Environment
Create a deployment .env beside compose.yml. Compose reads this file for variable substitution; it is excluded from Git and the Docker build context.
Minimal example using the persistent JWT secret generated in /app/data:
APP_PORT=8000
JWT_EXPIRE_HOURS=24
LLM_TIMEOUT_SECONDS=180
TRANSLATION_CHUNK_CHARS=1500
Before the first startup of a new volume, add deployment-specific bootstrap credentials:
BOOTSTRAP_ADMIN_ID=admin
BOOTSTRAP_ADMIN_PASSWORD=replace-with-a-long-random-password
The bootstrap variables are only used when the database contains no users. Remove them from .env after the first successful startup and recreate the container with docker compose up -d --force-recreate.
By default, the application creates /app/data/.jwt-secret with mode 0600. Because the data directory is a named volume, tokens remain valid across container replacement. To manage the secret externally instead, set a stable random value:
JWT_SECRET_KEY=replace-with-at-least-48-random-bytes
Do not rotate JWT_SECRET_KEY unintentionally; rotation invalidates existing login tokens. Generate values with a secrets manager or a command such as openssl rand -base64 48, and do not commit the resulting .env.
Configuration
config/llms.json is required and readiness fails when it contains no model definitions. It may contain API keys, so limit host access while ensuring UID 10001 in the container can read the bind-mounted file. Compose imports the empty config/users.docker.json once and creates the first administrator from bootstrap variables. Later user administration is stored in SQLite. A custom legacy file containing password_plain must be migrated once outside the read-only container so the plaintext can be removed.
The container receives these fixed paths:
DATABASE_PATH=/app/data/translator.db
JWT_SECRET_FILE=/app/data/.jwt-secret
LLMS_CONFIG_PATH=/app/config/llms.json
USERS_CONFIG_PATH=/app/config/users.json
Start And Verify
Build and start the service:
docker compose up -d --build
docker compose ps
docker compose logs -f translator
The service is available at http://localhost:${APP_PORT:-8000}. Docker checks GET /api/health/ready every 30 seconds. This readiness check validates SQLite access and that at least one LLM configuration loaded; it does not contact the remote LLM endpoint.
To test readiness directly:
curl --fail http://localhost:8000/api/health/ready
Operations
Deploy an updated image with:
docker compose build --pull
docker compose up -d
Stop without deleting persistent data:
docker compose down
Do not add --volumes unless the database and generated JWT secret should be permanently removed. Compose allows 190 seconds for shutdown, while Uvicorn allows in-flight requests up to 180 seconds before forcing shutdown.
Back up the named volume before upgrades. For a consistent simple backup, stop the service first and copy the volume contents with your standard Docker volume backup tooling. The volume contains the SQLite database, WAL files when present, and the generated JWT secret.
Reverse Proxy
Terminate TLS in a reverse proxy and forward traffic to port 8000. Preserve the same origin for the frontend and /api routes. Do not expose the LLM configuration files, data volume, or Docker socket through the proxy.