← All posts
ReleaseOct 25, 2026· 2 min read· Kanawoot K.

Easy CMS 1.7: ready to run on several servers

Three releases since 1.4 make Easy CMS ready to run on several servers: Postgres poolers, JSON logs with request ids, health checks, and an official Docker image.

Easy CMS 1.4 made background work run once however many servers share a database. Three releases since finish the job: everything a team needs to run Easy CMS on several servers behind a load balancer, and to know what it is doing there. None of them breaks anything, and none needs a migration.

A setup that is tested, not assumed

The new Reference architecture page says what is supported when one server isn't enough: Postgres, S3-compatible storage for uploads, any number of identical instances behind a load balancer without sticky sessions, and a CDN in front. It also says plainly what is single-server only (SQLite on a disk, uploads on a disk) and what isn't supported (several regions, read replicas).

CI now checks this on every change. It starts two instances on one Postgres and one bucket behind a round-robin proxy, logs in on one and uses the session on the other, uploads through one and downloads through the other, counts failed logins across both, and runs scheduled publishing on both at once to check each post is published once.

Postgres connection options (1.5)

postgres() takes prepare, statementTimeoutMs, idleTimeoutMs, connectTimeoutMs and applicationName. Connections show up as easy-cms in pg_stat_activity.

db: postgres({ url: process.env.DATABASE_POOLER_URL, max: 2, prepare: false })

On serverless hosts and with many instances, connect through a pooler. CI runs every Postgres test through PgBouncer in transaction mode. The Databases guide explains pool sizes, and when to turn off prepared statements.

Organisations that want the CMS kept apart from business data can give it a schema and a role of its own. The new Database isolation guide shows four levels, from a shared database to a server of its own, with the SQL to move an existing site.

JSON logs and request ids (1.6)

logs: { format: 'json' }

Or set EASY_CMS_LOG_FORMAT=json on the host. Every line becomes one JSON object, with the time, level, message and the error's stack, ready for Datadog, Loki or CloudWatch. Every API response carries an X-Request-Id header, every line logged while answering it has the same id, and an id your load balancer sends is kept. With JSON logs each request also gets a line with its path, status and duration. See Logs.

Health checks (1.7)

Every setup now answers two probes under the API:

  • /api/cms/healthz answers ok while the process runs, without touching the database.

  • /api/cms/readyz answers 200 when the database answers and no migration is pending, and 503 otherwise.

Point the load balancer, or Kubernetes' readiness probe, at readyz, and liveness at healthz. They answer even when the CMS can't start, and stay out of the request log. See Health checks.

An official Docker image

ghcr.io/maritonx/easy-cms runs easy-cms serve with the SQLite and Postgres adapters, S3 storage and SMTP email. Add your config and migrations:

FROM ghcr.io/maritonx/easy-cms:1
COPY easy-cms.config.ts ./
COPY easy-cms ./easy-cms

It runs as a non-root user, checks /readyz, and comes for amd64 and arm64. Each release builds it. See Docker.

Also

  • Logging out on a page with unsaved changes asks first, and staying keeps you logged in (1.4.3).

  • Reads with locale: 'all' are typed as they return (1.4.2).

Upgrading

Upgrade every package together. There is no migration:

npm install @easy-cms/core@latest easy-cms@latest

The changelog lists every release.