Docker Deployment
Docker is the recommended deployment method for most users. Codex provides pre-built Docker images that include the web frontend.
Quick Start with Docker Run
The simplest way to run Codex is with a single docker run command using SQLite:
docker run -d \
--name codex \
-p 8080:8080 \
-v /path/to/your/library:/library:ro \
-v codex-data:/app/data \
-e PUID=1000 \
-e PGID=1000 \
-e CODEX_AUTH__JWT_SECRET="$(openssl rand -base64 32)" \
ghcr.io/ashdevfr/codex:latest
Access Codex at http://localhost:8080. On first launch, you'll be guided through a setup wizard to create your admin account.
:::tip Finding Your User ID
Run id in your terminal to find your UID and GID. Use these values for PUID and PGID to avoid permission issues with mounted volumes.
:::
Replace /path/to/your/library with the path to your comics, manga, or ebooks folder.
Understanding the Volumes
| Volume | Container Path | Purpose |
|---|---|---|
| Your library | /library | Your comics, manga, and ebooks (read-only) |
| codex-data | /app/data | SQLite database, thumbnails, and uploads |
:::tip Library Permissions
Mount your media library as read-only (:ro) to prevent accidental modifications. Codex only needs read access to your files.
:::
Quick Start with Docker Compose
For a more maintainable setup, use Docker Compose. Create a docker-compose.yml file:
services:
codex:
image: ghcr.io/ashdevfr/codex:latest
container_name: codex
ports:
- "8080:8080"
volumes:
# Your media library (read-only)
- /path/to/your/library:/library:ro
# Codex data: SQLite database, thumbnails, uploads
- codex-data:/app/data
environment:
# User/Group ID for file permissions (run `id` to find yours)
PUID: 1000
PGID: 1000
# Generate with: openssl rand -base64 32
CODEX_AUTH__JWT_SECRET: "your-secure-secret-here"
restart: unless-stopped
volumes:
codex-data:
Then start Codex:
# Start Codex
docker compose up -d
# View logs
docker compose logs -f codex
Access Codex at http://localhost:8080. On first launch, you'll be guided through a setup wizard to create your admin account.
:::tip Finding Your User ID
Run id in your terminal to find your UID and GID. Use these values for PUID and PGID to avoid permission issues with mounted volumes.
:::
Multiple Libraries
To add multiple library paths, mount each one separately:
services:
codex:
image: ghcr.io/ashdevfr/codex:latest
container_name: codex
ports:
- "8080:8080"
volumes:
# Multiple libraries
- /mnt/comics:/library/comics:ro
- /mnt/manga:/library/manga:ro
- /mnt/ebooks:/library/ebooks:ro
# Codex data
- codex-data:/app/data
environment:
CODEX_AUTH__JWT_SECRET: "your-secure-secret-here"
restart: unless-stopped
volumes:
codex-data:
Then create libraries in the Codex UI pointing to /library/comics, /library/manga, etc.
Using a Bind Mount for Data
If you prefer using a local directory instead of a Docker volume for Codex data:
services:
codex:
image: ghcr.io/ashdevfr/codex:latest
container_name: codex
ports:
- "8080:8080"
volumes:
- /path/to/your/library:/library:ro
# Use a local directory for data
- ./codex-data:/app/data
environment:
CODEX_AUTH__JWT_SECRET: "your-secure-secret-here"
restart: unless-stopped
This makes it easier to backup the database and thumbnails.
PostgreSQL Setup (Optional)
For larger libraries or multi-user setups, you can use PostgreSQL:
services:
postgres:
image: postgres:16-alpine
container_name: codex-postgres
environment:
POSTGRES_USER: codex
POSTGRES_PASSWORD: codex
POSTGRES_DB: codex
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U codex"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
codex:
image: ghcr.io/ashdevfr/codex:latest
container_name: codex
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
volumes:
- /path/to/your/library:/library:ro
- codex-data:/app/data
environment:
CODEX_AUTH__JWT_SECRET: "your-secure-secret-here"
CODEX_DATABASE__DB_TYPE: postgres
CODEX_DATABASE__POSTGRES__HOST: postgres
CODEX_DATABASE__POSTGRES__PORT: 5432
CODEX_DATABASE__POSTGRES__USERNAME: codex
CODEX_DATABASE__POSTGRES__PASSWORD: codex
CODEX_DATABASE__POSTGRES__DATABASE_NAME: codex
# CODEX_SCHEDULER__TIMEZONE: America/Los_Angeles # IANA timezone for cron jobs (default: UTC)
restart: unless-stopped
volumes:
postgres-data:
codex-data:
:::tip Timezone for Cron Jobs
The Docker TZ environment variable does not affect Codex cron scheduling. To run scheduled library scans in your local timezone, set CODEX_SCHEDULER__TIMEZONE to a valid IANA timezone name (e.g., America/Los_Angeles, Europe/London). Each library can also override this with its own timezone in the scanning configuration.
:::
Separate Worker Containers (Advanced)
For high-performance setups or when you need to scale workers independently from the web server, you can run dedicated worker containers. This requires PostgreSQL.
:::info Why Separate Workers? Separating workers from the web server allows you to:
- Scale background task processing independently
- Isolate resource-intensive tasks (scanning, thumbnail generation) from API requests
- Run more workers than web server instances :::
This example uses YAML anchors (x-codex-common) to share configuration between services:
# Shared configuration for all Codex services
x-codex-common: &codex-common
image: ghcr.io/ashdevfr/codex:latest
volumes:
- /path/to/your/library:/library:ro
- codex-data:/app/data
environment: &codex-env
CODEX_AUTH__JWT_SECRET: "your-secure-secret-here"
CODEX_DATABASE__DB_TYPE: postgres
CODEX_DATABASE__POSTGRES__HOST: postgres
CODEX_DATABASE__POSTGRES__PORT: 5432
CODEX_DATABASE__POSTGRES__USERNAME: codex
CODEX_DATABASE__POSTGRES__PASSWORD: codex
CODEX_DATABASE__POSTGRES__DATABASE_NAME: codex
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
services:
postgres:
image: postgres:16-alpine
container_name: codex-postgres
environment:
POSTGRES_USER: codex
POSTGRES_PASSWORD: codex
POSTGRES_DB: codex
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U codex"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# Web server (API only, no workers)
codex:
<<: *codex-common
container_name: codex-web
ports:
- "8080:8080"
environment:
<<: *codex-env
# Workers run in their own container
CODEX_TASK__RUN_IN_PROCESS: "false"
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
# Dedicated worker container
codex-worker:
<<: *codex-common
container_name: codex-worker
# Run the worker command instead of serve
command: ["codex", "worker"]
environment:
<<: *codex-env
# Number of parallel task workers
CODEX_TASK__WORKER_COUNT: "4"
# The web container applies migrations; this one waits for them
CODEX_DATABASE__RUN_MIGRATIONS: "false"
depends_on:
postgres:
condition: service_healthy
codex:
condition: service_healthy
volumes:
postgres-data:
codex-data:
Key Configuration Options
| Variable | Description | Default |
|---|---|---|
CODEX_TASK__RUN_IN_PROCESS | Run background workers in this container | true |
CODEX_DATABASE__RUN_MIGRATIONS | Apply migrations at startup; when false, wait for them | true |
CODEX_TASK__WORKER_COUNT | Number of parallel task workers | 2 |
Scaling Workers
To run multiple worker instances:
docker compose up -d --scale codex-worker=3
:::warning SQLite Not Supported Separate worker containers require PostgreSQL. SQLite does not support multiple processes accessing the database simultaneously. :::
Volume Considerations
| Volume | Purpose | Permissions |
|---|---|---|
/app/data | Database (SQLite), thumbnails, uploads | Read-write |
/library | Media files | Read-only (recommended) |
Health Checks
You can add health checks to your Docker Compose configuration:
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
interval: 10s
timeout: 5s
retries: 5
User/Group Permissions (PUID/PGID)
Codex supports PUID and PGID environment variables to run with specific user/group permissions. This is essential when using bind mounts to ensure the container can read/write to your host directories.
| Variable | Description | Default |
|---|---|---|
PUID | User ID to run as | 1000 |
PGID | Group ID to run as | 1000 |
To find your user's UID and GID:
id
# Output: uid=1000(youruser) gid=1000(youruser) ...
Example with Custom PUID/PGID
services:
codex:
image: ghcr.io/ashdevfr/codex:latest
environment:
PUID: 1000
PGID: 1000
CODEX_AUTH__JWT_SECRET: "your-secret-here"
volumes:
- ./codex-data:/app/data
- ./codex-config:/app/config
- /mnt/media:/library:ro
:::tip Synology NAS Users
Synology DSM often uses different UIDs. Check your user's ID with id via SSH, or use the admin user's UID (usually 1026 or similar).
:::
Environment Variables
Common environment variables for Docker:
| Variable | Description | Example |
|---|---|---|
PUID | User ID for file permissions | 1000 |
PGID | Group ID for file permissions | 1000 |
CODEX_AUTH__JWT_SECRET | JWT signing secret | your-secret-key |
CODEX_DATABASE__DB_TYPE | Database type | postgres or sqlite |
CODEX_DATABASE__POSTGRES__HOST | PostgreSQL host | postgres |
CODEX_DATABASE__POSTGRES__PASSWORD | PostgreSQL password | secret |
CODEX_LOGGING__LEVEL | Log level | info, debug |
See Configuration for all options.
Updating
# If using docker run
docker pull ghcr.io/ashdevfr/codex:latest
docker stop codex
docker rm codex
# Then run the docker run command again
# If using Docker Compose
docker compose pull
docker compose up -d
# Check logs for migration status
docker compose logs codex
Troubleshooting
Container Won't Start
# Check logs
docker compose logs codex
# Or for docker run
docker logs codex
Database Connection Issues
# Test PostgreSQL connection
docker compose exec postgres psql -U codex -d codex -c "SELECT 1"
# Check network
docker compose exec codex ping postgres
Permission Issues
If you see Permission denied (os error 13) errors:
-
Check your host user's UID/GID:
id# uid=1000(user) gid=1000(user) ... -
Set PUID/PGID in your docker-compose.yml:
environment:PUID: 1000 # Your UID from step 1PGID: 1000 # Your GID from step 1 -
Verify permissions inside the container:
docker compose exec codex ls -la /app/datadocker compose exec codex ls -la /app/configdocker compose exec codex ls -la /library
The container will automatically adjust the internal user to match your PUID/PGID, ensuring it can write to mounted volumes.