Skip to main content

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.

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

VolumeContainer PathPurpose
Your library/libraryYour comics, manga, and ebooks (read-only)
codex-data/app/dataSQLite database, thumbnails, and uploads
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.

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
restart: unless-stopped

volumes:
postgres-data:
codex-data:

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.

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
# Disable workers in the web container
CODEX_DISABLE_WORKERS: "true"
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"
# Skip migrations (web container handles them)
CODEX_SKIP_MIGRATIONS: "true"
depends_on:
postgres:
condition: service_healthy
codex:
condition: service_healthy

volumes:
postgres-data:
codex-data:

Key Configuration Options

VariableDescriptionDefault
CODEX_DISABLE_WORKERSDisable background workers in web containerfalse
CODEX_SKIP_MIGRATIONSSkip database migrations on startupfalse
CODEX_TASK_WORKER_COUNTNumber of parallel task workers4

Scaling Workers

To run multiple worker instances:

docker compose up -d --scale codex-worker=3
SQLite Not Supported

Separate worker containers require PostgreSQL. SQLite does not support multiple processes accessing the database simultaneously.

Volume Considerations

VolumePurposePermissions
/app/dataDatabase (SQLite), thumbnails, uploadsRead-write
/libraryMedia filesRead-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.

VariableDescriptionDefault
PUIDUser ID to run as1000
PGIDGroup ID to run as1000

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
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:

VariableDescriptionExample
PUIDUser ID for file permissions1000
PGIDGroup ID for file permissions1000
CODEX_AUTH_JWT_SECRETJWT signing secretyour-secret-key
CODEX_DATABASE_DB_TYPEDatabase typepostgres or sqlite
CODEX_DATABASE_POSTGRES_HOSTPostgreSQL hostpostgres
CODEX_DATABASE_POSTGRES_PASSWORDPostgreSQL passwordsecret
CODEX_LOGGING_LEVELLog levelinfo, 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:

  1. Check your host user's UID/GID:

    id
    # uid=1000(user) gid=1000(user) ...
  2. Set PUID/PGID in your docker-compose.yml:

    environment:
    PUID: 1000 # Your UID from step 1
    PGID: 1000 # Your GID from step 1
  3. Verify permissions inside the container:

    docker compose exec codex ls -la /app/data
    docker compose exec codex ls -la /app/config
    docker 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.