Deploying Wikantik with Docker

🌐 Product overview: Self-hosting & backup on wikantik.com — a plain-language walkthrough for readers and AI agents.

Wikantik is deployed as a Docker Compose stack: PostgreSQL with pgvector, the Wikantik application on Tomcat 11, and, in the production overlay, a backup sidecar. The repository ships the real artifacts (Dockerfile, docker-compose*.yml, docker/), so do not hand-write compose files. This page is a summary; the operator reference is docs/admin/DockerDeployment.md in the repository, with docs/admin/BackupAndRecovery.md for backups and docs/admin/CloudDeployment.md for the AWS/GCP VM overlay.

1. What Ships

The image. The Dockerfile is multi-stage. It builds the WAR (including the React frontend) in maven:3.9-eclipse-temurin-25, then runs it on tomcat:11.0.26-jdk25-temurin (Tomcat 11, Java 25). The WAR is unzipped as the ROOT context, so the wiki answers at http://host:8080/. The PostgreSQL JDBC driver is downloaded from Maven Central at build time (ARG POSTGRES_JDBC_VERSION, currently 42.7.14, which must match <postgresql-jdbc.version> in the root pom.xml). The image also carries the database migrations (bin/db/) and the default pages from docs/wikantik-pages/. Defaults: CATALINA_OPTS="-Djava.security.egd=file:/dev/./urandom -XX:MaxRAMPercentage=70.0 --add-modules=jdk.incubator.vector" and LANG=en_US.UTF-8. The container healthcheck is curl -fsS http://localhost:8080/api/health.

The services.

ServiceImageRole
dbpgvector/pgvector:pg18PostgreSQL with the vector extension: users, groups, policy grants, Knowledge Graph, embeddings, page metadata, schema_migrations
wikantikwikantik:latestTomcat 11 / JDK 25 running the wiki
backuppostgres:18-alpineScheduled pg_dump and page-tree tarball (production overlay)

The production overlay bundles no embedding service. Production embeds, and runs Knowledge Graph extraction, on the shared GPU inference host: set WIKANTIK_EMBEDDING_BASE_URL and WIKANTIK_EXTRACTOR_BASE_URL in .env to a name that resolves from the container (for example http://inference.lan:11434). If the embedding endpoint is unreachable, retrieval falls back to BM25.

The compose files. docker-compose.yml defines db and wikantik; docker-compose.dev.yml, docker-compose.prod.yml and docker-compose.test.yml are overlays, and docker-compose.cloud.yml is the cloud overlay. bin/container.sh -e dev|prod|test|base wraps docker compose over them (build, up -d, logs -f, psql, migrate, backup, restore), and bin/remote.sh drives a remote host over ssh.

Quick start.

cp .env.example .env                  # set POSTGRES_PASSWORD (never leave CHANGEME) and WIKANTIK_BASE_URL
bin/container.sh -e base build        # db + wikantik only, named volumes
bin/container.sh -e base up -d
curl -fsS http://localhost:8080/api/health

Use -e prod for a real host: it adds the backup sidecar, resource limits and the host bind mounts, so set WIKANTIK_PAGES_DIR and BACKUP_DIR first. bin/container.sh smoke-test brings up a throwaway test stack on port 18080 and checks /api/health.

On a fresh database the entrypoint prints the first-login banner: log in as admin / admin123 and you are required to choose a new password.

2. Configuration

There are no jspwiki_* environment variables any more. Compose passes .env to the wikantik service, and on every start docker/entrypoint.sh renders three files from the container environment: wikantik-custom.properties (in Tomcat's lib/), conf/Catalina/localhost/ROOT.xml (the JNDI DataSource jdbc/WikiDatabase) and wikantik-mcp.properties. It then applies any pending database migrations and starts Tomcat. To change configuration, edit .env and redeploy; never edit files inside a running container.

VariableDefaultEffect
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORDwikantik, wikantik, CHANGEMEDatabase credentials (POSTGRES_HOST is db and POSTGRES_PORT is 5432 inside the stack)
WIKANTIK_BASE_URLhttp://localhost:8080/External URL: sitemap, canonical links, SSO callback
WIKANTIK_PAGE_DIR, WIKANTIK_WORK_DIR, WIKANTIK_ATTACHMENT_DIR/var/wikantik/pages, /var/wikantik/work, /var/wikantik/pagesPage tree, work directory (search index), attachment store
WIKANTIK_COOKIE_AUTHENTICATIONfalseRemember-me re-authentication across restarts
WIKANTIK_SSO_ENABLED and the other WIKANTIK_SSO_*offOIDC single sign-on; see Google SSO
WIKANTIK_GENAI_MODEfullGenAI ceiling: full, embeddings-only or none
WIKANTIK_KNOWLEDGE_ENABLEDtruefalse skips the Knowledge Graph subsystem
WIKANTIK_DENSE_BACKENDlucene-hnswDense retrieval backend: lucene-hnsw, inmemory or pgvector
WIKANTIK_EMBEDDING_BASE_URLhttp://inference.jakefear.com:11434Embedding service. The shipped default does not resolve on every network, so set it to a name that resolves from the container (production uses the shared GPU inference host, e.g. http://inference.lan:11434); falls back to BM25 if unreachable
WIKANTIK_EXTRACTOR_BACKENDollamaKnowledge Graph extractor: ollama, claude (needs ANTHROPIC_API_KEY) or disabled
WIKANTIK_MAX_INFLIGHT_REQUESTS390Concurrency cap; excess requests get 503 with Retry-After: 1
WIKANTIK_JVM_MAX_RAM_PERCENTAGEimage default 70Heap share of the container memory limit (the production overlay sets 50 with a 3G limit)
PROXY_REMOTE_IP_HEADERCF-Connecting-IPHeader carrying the real client IP; use X-Forwarded-For behind Caddy, nginx or a cloud load balancer
MAIL_SMTP_HOST (and MAIL_SMTP_PORT, MAIL_SMTP_ACCOUNT, MAIL_SMTP_PASSWORD, MAIL_FROM)emptyEmail is off until the host is set
WIKANTIK_SCIM_TOKENunsetSCIM bearer token; SCIM denies everything without it
WIKANTIK_SEED_DEV_USERSfalseSeeds the default admin; never set it in production

MCP and /tools/* clients are authorized with database-backed API keys minted at /admin/apikeys; there is no environment variable that grants access. See OpenWebUI Tool Server. The full variable list, with every default, is in docs/admin/DockerDeployment.md.

3. Data Persistence

User accounts and groups live in PostgreSQL (see Relational User Database), not in files. Mount these so state survives container replacement:

4. Automated Backups

The production overlay runs a postgres:18-alpine sidecar with BusyBox crond. Its schedule is docker/backup/crontab, and each entry runs docker/backup/backup.sh <tier>:

TierScheduleRetention
daily02:00 every day30 days (BACKUP_RETENTION_DAYS)
weekly03:00 on Sundays12 weeks
monthly04:00 on the 1st12 months

Each run writes ${BACKUP_DIR}/<tier>/YYYY-MM-DD/ containing db.sql.gz (a pg_dump of the whole database), pages.tar.gz, checksums.sha256 and backup-status.json, and updates a LATEST pointer. It also writes Prometheus textfile metrics such as wikantik_backup_last_success_timestamp_seconds{tier} into BACKUP_METRICS_DIR; those gauges are what the jakemon alerts watch (see IncidentWikantikBackupStale).

bin/container.sh -e prod backup weekly     # run a tier now
bin/remote.sh backup-trigger daily         # the same, on the remote host
bin/remote.sh backup-pull                  # fetch the newest daily snapshot

An off-box copy is pulled by bin/backup/nas-pull.sh on a separate host (a daily systemd timer); the full 3-2-1 design is in docs/admin/BackupAndRecovery.md.

5. Restoring from a Backup

  1. Stop the application so nothing writes during the restore:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml stop wikantik
    
  2. Run restore.sh in the backup sidecar. It verifies the checksums first and aborts on a mismatch, drops and rebuilds the public schema, loads db.sql.gz, and restores the page tree:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml \
        exec backup /usr/local/bin/restore.sh /backups/daily/<YYYY-MM-DD>
    
  3. Start the application again; the search index rebuilds on startup:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d wikantik
    

bin/container.sh -e prod restore /backups/daily/<YYYY-MM-DD> runs step 2 only; stopping and starting the application stays with you. To stand up a whole new host from a snapshot use bin/dr-restore.sh, and to prove a snapshot restores cleanly without touching production use bin/backup/verify-restore.sh.

6. Releases and Rollback

Release images are published to GHCR as ghcr.io/jakefearsd/wikantik:X.Y.Z. bin/deploy-release.sh X.Y.Z pulls an image and runs bin/remote.sh deploy --skip-build, which tags the running image :rollback, ships the new one over ssh, runs up -d, polls /api/health, and re-promotes :rollback automatically if the poll fails. Schema migrations run on container start, so an upgrade is an image swap; only a PostgreSQL major-version change needs a dump and restore.

PostgreSQL is published only on the docker0 bridge in the production overlay (DB_HOST_BIND, default 172.17.0.1), and /api/health and /metrics answer only to loopback and private-network clients.


Cluster Hub: Cloud Platforms Hub: AWS, GCP, Azure, and Multi-Cloud Architecture (cloud-platforms)