Run with Docker
The server ships as a single, self-contained image. A consumer only needs
docker pull and docker run — no repository, Node, or PDF toolchain. The Node
22 runtime, native PDFium, image processing, SQLite, and fonts are all baked in.
Images are published to GitHub Container Registry:
ghcr.io/embedpdf/cloudpdf-serverRun it#
The zero-config profile uses SQLite, the local filesystem, and a local cache,
all under the /data volume:
docker run --rm --init -p 3000:3000 \
-v cloudpdf-data:/data \
-e CLOUDPDF_LICENSE_KEY="key/..." \
ghcr.io/embedpdf/cloudpdf-server:latestThe server is fail-closed on licensing: CLOUDPDF_LICENSE_KEY is required
(a development key keeps everything
else zero-config for local runs — even the JWT secret falls back to a dev
default, with a warning).
--initgives the container correct signal and zombie handling. The server already handlesSIGTERM/SIGINT, so it stops cleanly.-v cloudpdf-data:/datapersists your data across restarts. Everything mutable lives under/data— the SQLite file, stored objects, and the cache.
Confirm it’s up:
curl localhost:3000/healthz # {"status":"ok"}
curl localhost:3000/readyz # {"status":"ok"}The built-in defaults#
The image sets production-friendly defaults so the zero-config run just works:
| Variable | Default in the image |
|---|---|
NODE_ENV | production |
PORT / HOST | 3000 / 0.0.0.0 |
CLOUDPDF_DB_SQLITE_PATH | /data/cloudpdf.db |
CLOUDPDF_STORAGE_FS_ROOT | /data/objects |
CLOUDPDF_CACHE_ROOT | /data/cache |
CLOUDPDF_AUTO_MIGRATE | 1 (migrate on boot) |
Override any of them with -e. The full list is in the
configuration reference.
The entrypoint is the CLI#
The image’s entrypoint is the cloudpdf-server binary and the default command is
serve, so you can run any subcommand by appending it:
docker run --rm -v cloudpdf-data:/data \
ghcr.io/embedpdf/cloudpdf-server:latest migrate status
docker run --rm -v cloudpdf-data:/data \
ghcr.io/embedpdf/cloudpdf-server:latest db doctorSee the CLI reference for all commands.
Production: external database and storage#
For production, point the container at Postgres and a cloud bucket, take migrations out of the boot path, and let the pool use your cores:
Generate the server secrets once and keep them in your secret manager — they must stay stable across restarts (see Encrypted-PDF secrets for what the last two protect):
export CLOUDPDF_LICENSE_KEY="key/..."
export CLOUDPDF_JWT_SECRET="$(openssl rand -hex 32)"
export CLOUDPDF_PASSWORD_VERIFICATION_HMAC_SECRET="$(openssl rand -hex 32)"
export CLOUDPDF_PASSWORD_SESSION_SERVER_SECRET="$(openssl rand -hex 32)"docker run -d --init -p 3000:3000 \
-e CLOUDPDF_LICENSE_KEY \
-e CLOUDPDF_JWT_SECRET \
-e CLOUDPDF_PASSWORD_VERIFICATION_HMAC_SECRET \
-e CLOUDPDF_PASSWORD_SESSION_SERVER_SECRET \
-e CLOUDPDF_DB_DRIVER=postgres \
-e CLOUDPDF_DB_URL="postgres://user:pass@db:5432/cloudpdf" \
-e CLOUDPDF_STORAGE_KIND=s3 \
-e CLOUDPDF_STORAGE_S3_BUCKET=my-cloudpdf-bucket \
-e CLOUDPDF_STORAGE_S3_REGION=eu-west-1 \
-e CLOUDPDF_AUTO_MIGRATE=0 \
-e CLOUDPDF_FAIL_ON_PENDING=1 \
-e CLOUDPDF_WORKER_POOL_SIZE=max \
ghcr.io/embedpdf/cloudpdf-server:1.0.0Run migrations as a separate, one-shot step before rolling out new containers — so replicas never race each other to migrate:
docker run --rm \
-e CLOUDPDF_DB_DRIVER=postgres \
-e CLOUDPDF_DB_URL="postgres://user:pass@db:5432/cloudpdf" \
ghcr.io/embedpdf/cloudpdf-server:1.0.0 migrate upManaging more than the server itself — Postgres, object storage, a migration step — is exactly what Docker Compose wires up for you.
Health checks#
The image includes a built-in HEALTHCHECK that polls /healthz, so
docker ps and your orchestrator can see container health without extra
configuration.
Image tags & versioning#
| Tag | Meaning |
|---|---|
:1.2.3 | An exact, immutable release. |
:1.2 | The latest patch in a minor line. |
:latest | The newest release. Convenient, but moves. |
:1.2.3-next.N, :next | Pre-release candidates from the preview channel. |
In production, pin a specific version — ideally by digest
(@sha256:…) — so every replica runs identical bytes. Never run
:latest in production.
Pin by digest:
docker pull ghcr.io/embedpdf/cloudpdf-server:1.0.0
docker run ... ghcr.io/embedpdf/cloudpdf-server@sha256:<digest>