2 Install
Ulysia edited this page 2026-10-06 16:54:26 +02:00

Mirrored from INSTALL.md at commit f31ec6a. Edit the file in the repository: this page is regenerated from it, and edits made here are overwritten.

Installing

Deploying Docmost Freenterprise with Docker. CI publishes the image, so the server needs neither the source nor a toolchain — two files and a directory.

Building the image yourself instead? See Building from source at the bottom. Working on the code? See SETUP-DEV.md.


The layout

.
|-- compose.yml          <- yours, from compose.example.yml
|-- .env                 <- yours, secrets live here
`-- data/                <- attachments, owned by uid 1000

That is all of it. No clone, no docmost/ directory, no patches to apply — the published image already has them baked in.

What you need first

  • Docker with the Compose plugin
  • Access to git.derg.cz's package registry, if the package is private

Postgres, Redis and the two sidecars all come from compose.


Quick start

Edit the first line, then paste the whole block. It fetches compose.yml and .env, generates the secrets, and creates the storage directory with the right owner. sudo at the end may prompt.

APP_URL="https://docs.example.com"     # <-- change this

BASE=https://git.derg.cz/ulysia/docmost-freenterprise/raw/branch/main
curl -fsSL "$BASE/compose.example.yml" -o compose.yml
curl -fsSL "$BASE/.env.example"        -o .env

sed -i "s|^APP_URL=.*|APP_URL=${APP_URL}|" .env
sed -i "s|^APP_SECRET=.*|APP_SECRET=$(openssl rand -hex 32)|" .env
sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" .env

mkdir -p data && sudo chown -R 1000:1000 data

Then, separately:

docker compose up -d

Open APP_URL once it settles and create the first account, which becomes the workspace owner.

If the package or repository is private, both the curls and the image pull need credentials — log in with a token that has package read scope, and add -H "Authorization: token <tok>" to the curl calls:

docker login git.derg.cz -u <you>

sed -i here is GNU sed. On macOS use sed -i ''.

Nothing else is required. The rest of this document explains what those commands did, and what else you can set.


1. compose.yml and .env

Both are templates in the bundle, fetched above. compose.yml is ready as-is; .env ships with placeholders, which the sed lines replace.

The image is git.derg.cz/ulysia/docmost-freenterprise:main, rebuilt on every push to the bundle's main branch. main moves. Pin a :<short-sha> tag when you want a deployment to stay put — every build publishes one, listed under the repository's Packages tab. Each image also carries a label recording which upstream Docmost commit it was built against, since the interesting version is not in either repository alone:

docker inspect --format '{{index .Config.Labels "cz.derg.freenterprise.upstream-commit"}}' \
  git.derg.cz/ulysia/docmost-freenterprise:main

2. Environment

Three values are required; everything else in .env is commented out with its default shown.

Variable
APP_URL Public URL, e.g. https://docs.example.com. Builds links in email and shares, so it must be what users actually type.
APP_SECRET 32+ characters. Changing it invalidates every session.
POSTGRES_PASSWORD Set once. compose.yml builds DATABASE_URL from it and hands the same value to the db container, so there is nothing to keep in sync.

To regenerate a secret later:

sed -i "s|^APP_SECRET=.*|APP_SECRET=$(openssl rand -hex 32)|" .env
docker compose up -d

Changing POSTGRES_PASSWORD after first start does not change the password Postgres already stored — the db container only reads it when it initialises an empty data directory. Change it inside Postgres, or start over.

The rest of .env is optional and grouped: Enterprise (SSO_ACCOUNT_MERGE, SSO_MERGE_REQUIRE_VERIFIED_EMAIL, SSO_OIDC_GROUP_SYNC) and Core (mail, storage driver, upload limit, telemetry). Uncomment what you need.

Service URLs — DATABASE_URL, REDIS_URL, GOTENBERG_URL, TIKA_URL, PDF_RENDER_URL — are deliberately not in .env. They are determined by the service names in compose.yml and are set there, so renaming a service cannot leave a stale URL behind.

3. The storage directory

mkdir -p data && sudo chown -R 1000:1000 data

Before the first start, not after. The container runs as node, uid 1000, and Docker creates a missing bind-mount source as root — leaving a directory the app cannot write to. Every upload then fails with Error uploading file to drive and a log line carrying nothing else, because core logs the real error in a way that discards it. DEPLOYMENT.md has the full diagnosis under Attachment storage.

uid 1000 numerically: the host has no node user, and only numbers cross the container boundary.

compose.yml uses a bind mount so attachments sit somewhere you can see and back up. A named volume avoids this step entirely — Docker seeds it from the image, where the directory is already owned correctly — and the comment in compose.yml shows the swap. Don't switch an existing install over; the files do not move themselves.

4. Start

docker compose up -d
docker compose logs -f docmost

Migrations run at startup, including the two the bundle adds.


Checking it worked

Check Where Wrong looks like
EE loaded at all Settings → Licence Should read Freenterprise and list every feature
Audit logging SELECT count(*) FROM audit; after logging in 0 means the no-op service won
Uploads Paste an image into a page See Attachment storage in DEPLOYMENT.md
PDF export Export any page A blank PDF means PDF_RENDER_URL is unreachable
API docs /openapi Swagger UI for every route the instance serves

VERIFICATION.md records what has and has not been tested; ISSUES.md records bugs found in live testing.


Updating

docker compose pull
docker compose up -d

:main is a moving tag, so pull is what advances it. On a pinned :<short-sha> tag, edit compose.yml to the new tag first.

Compare your compose.yml and .env against the templates after an update — new options show up there first.


Building from source

Only needed if you are changing the code, or deliberately want to build the image rather than pull it. This is the long way round: it needs both repositories, generates a wrapper Dockerfile, and applies the patch stack at build time.

git clone https://github.com/docmost/docmost.git
cd docmost

# The pinned upstream commit — NOT the v0.95.0 tag, which is 13 commits behind
# and predates the Node 26 / pnpm 11 upgrade. Also in upstream.pin, which is
# what CI reads. DEPLOYMENT.md explains the pin.
git checkout 7bef7b1a

git clone ssh://git@git.derg.cz/ulysia/docmost-freenterprise.git apps/server/src/ee

# Upstream still references its own private `ee` submodule and does not ignore
# ours. Both need fixing:
git rm --cached apps/server/src/ee 2>/dev/null || true
rm -f .gitmodules
printf '\n# EE bundle is its own repository\napps/server/src/ee/\n' >> .gitignore
git status --short        # must NOT list apps/server/src/ee

chmod +x apps/server/src/ee/scripts/*.sh
apps/server/src/ee/scripts/gen-docker-wrapper.sh
cd ..

cp docmost/apps/server/src/ee/compose.dev.yml compose.yml
cp docmost/apps/server/src/ee/.env.example .env
# then fill in .env and create ./data exactly as above

docker compose up -d --build

On a Windows host use the PowerShell twin of the generator — no chmod needed, and it handles the CRLF traps that otherwise break a Dockerfile generated on Windows:

pwsh apps/server/src/ee/scripts/gen-docker-wrapper.ps1

Both read the same template and produce byte-identical output.

compose.dev.yml differs from compose.example.yml in exactly one service: docmost builds from ./docmost with the generated Dockerfile instead of pulling an image. Everything else is the same, ./data bind mount included.

Re-run the generator after editing the Dockerfile or moving to a new upstream commit. If a patch no longer applies the build stops and names it; patches/README.md explains how to regenerate one. The submodule and gitignore edits live outside git by design, so changing commits drops them — reapply those too.