Add Docker support with docker-compose and automated release workflow

- Add Dockerfile (Node Alpine, unprivileged user, configurable via env vars)
- Add docker-compose.yml with named volume and environment variable support
- Add docker/entrypoint.sh for wiki init and --listen argument assembly
- Add bin/docker-publish.sh for manual multi-platform image publishing
- Add .github/workflows/docker-publish.yml for automated Docker Hub release on GitHub Release publish
- Add editions/tw5.com howto tiddler documenting Docker usage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
himmel 2026-03-18 09:49:44 +00:00
parent e1cf523e2c
commit 25f0bf2cf0
7 changed files with 384 additions and 0 deletions

26
.dockerignore Normal file
View file

@ -0,0 +1,26 @@
# Version control
.git
.gitignore
.gitattributes
# Node dependencies (re-installed during build)
node_modules
# CI/CD and tooling
.github
.eslintrc*
.eslintignore
*.eslint*
# Test output and coverage
output
*.test.js
# Documentation source (not needed at runtime)
tw5.com
editions/tw5.com
# Misc
*.md
CNAME
LICENSE

76
.github/workflows/docker-publish.yml vendored Normal file
View file

@ -0,0 +1,76 @@
name: Publish Docker Image
on:
release:
types: [published] # triggers when a GitHub Release is published
workflow_dispatch: # allow manual trigger from the Actions tab
inputs:
tag:
description: "TiddlyWiki version tag to publish (e.g. v5.3.6)"
required: true
jobs:
publish:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write # needed if also pushing to GitHub Container Registry
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
ref: ${{ github.event.release.tag_name || inputs.tag }}
- name: Resolve version
id: version
run: |
# Strip leading 'v' from tag (v5.3.6 → 5.3.6)
RAW="${{ github.event.release.tag_name || inputs.tag }}"
echo "value=${RAW#v}" >> "$GITHUB_OUTPUT"
- name: Set up QEMU (for multi-platform builds)
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
# Optional: also push to GitHub Container Registry (ghcr.io)
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker tags
id: meta
uses: docker/metadata-action@v5
with:
images: |
tiddlywiki/tiddlywiki
ghcr.io/${{ github.repository }}
tags: |
type=raw,value=${{ steps.version.outputs.value }}
type=semver,pattern={{major}}.{{minor}},value=${{ steps.version.outputs.value }}
type=semver,pattern={{major}},value=${{ steps.version.outputs.value }}
type=raw,value=latest
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
NODE_LTS_VERSION=22
cache-from: type=gha
cache-to: type=gha,mode=max

47
Dockerfile Normal file
View file

@ -0,0 +1,47 @@
ARG NODE_LTS_VERSION=22
FROM node:${NODE_LTS_VERSION}-alpine
# Build arguments (overridable at build time)
ARG TIDDLYWIKI_DATA_DIR=/data
ARG TIDDLYWIKI_PORT=8080
# Runtime environment variables
ENV TIDDLYWIKI_DATA_DIR=${TIDDLYWIKI_DATA_DIR} \
TIDDLYWIKI_PORT=${TIDDLYWIKI_PORT} \
TIDDLYWIKI_HOST=0.0.0.0 \
TIDDLYWIKI_USERNAME="" \
TIDDLYWIKI_PASSWORD="" \
TIDDLYWIKI_READERS="(anon)" \
TIDDLYWIKI_WRITERS="(authenticated)"
WORKDIR /app
# Install production dependencies only (no network install of tiddlywiki itself)
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
# Copy source
COPY . .
# Symlink CLI so 'tiddlywiki' is available on PATH
RUN chmod +x tiddlywiki.js && \
ln -s /app/tiddlywiki.js /usr/local/bin/tiddlywiki
# Create unprivileged user with fixed UID/GID 1000 (matches the default first user
# on most Linux hosts, so bind-mounted directories work without --user override)
RUN addgroup -g 1000 -S tiddlywiki && \
adduser -u 1000 -S -G tiddlywiki tiddlywiki && \
mkdir -p "${TIDDLYWIKI_DATA_DIR}" && \
chown tiddlywiki:tiddlywiki "${TIDDLYWIKI_DATA_DIR}"
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
USER tiddlywiki
WORKDIR ${TIDDLYWIKI_DATA_DIR}
EXPOSE ${TIDDLYWIKI_PORT}
# Use Docker's built-in init process (docker run --init / compose: init: true)
ENTRYPOINT ["/entrypoint.sh"]

31
bin/docker-publish.sh Executable file
View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Publish an official TiddlyWiki Docker image to Docker Hub.
#
# Usage:
# ./bin/docker-publish.sh <tiddlywiki-version> [node-lts-version]
#
# Example:
# ./bin/docker-publish.sh 5.3.6
# ./bin/docker-publish.sh 5.3.6 22
#
# Requires:
# - docker buildx with multi-platform support (linux/amd64, linux/arm64)
# - docker login to Docker Hub as tiddlywiki org member
set -euo pipefail
TIDDLYWIKI_VERSION="${1:?Usage: $0 <tiddlywiki-version> [node-lts-version]}"
NODE_LTS_VERSION="${2:-22}"
REPO="tiddlywiki/tiddlywiki"
echo "Publishing ${REPO}:${TIDDLYWIKI_VERSION} (Node ${NODE_LTS_VERSION}-alpine) ..."
docker buildx build \
--platform linux/amd64,linux/arm64 \
--build-arg NODE_LTS_VERSION="${NODE_LTS_VERSION}" \
--tag "${REPO}:${TIDDLYWIKI_VERSION}" \
--tag "${REPO}:latest" \
--push \
.
echo "Done. Image available at: https://hub.docker.com/r/${REPO}"

38
docker-compose.yml Normal file
View file

@ -0,0 +1,38 @@
services:
tiddlywiki:
build:
context: .
args:
NODE_LTS_VERSION: "22"
# Or use the published image:
# image: tiddlywiki/tiddlywiki:latest
init: true # Docker's built-in init process for proper signal handling
ports:
- "127.0.0.1:8080:8080"
volumes:
# Named volume (default, Docker manages storage):
- tiddlywiki-data:/data
# Bind mount (map a host directory):
# - ./my-wiki:/data
# If using bind mount, the container runs as UID/GID 1000 by default.
# On most Linux systems this matches the first non-root user, so no extra
# chown is needed. If your host user has a different UID, either:
# mkdir -p ./my-wiki && chown 1000:1000 ./my-wiki
# or override at runtime:
# user: "$(id -u):$(id -g)"
environment:
TIDDLYWIKI_PORT: "8080"
TIDDLYWIKI_HOST: "0.0.0.0"
# Authentication — set these (or use a .env file)
TIDDLYWIKI_USERNAME: ""
TIDDLYWIKI_PASSWORD: ""
# Access control:
# (anon) — all users including anonymous
# (authenticated) — any logged-in user
# (admin) — admin users only
TIDDLYWIKI_READERS: "(anon)"
TIDDLYWIKI_WRITERS: "(authenticated)"
restart: unless-stopped
volumes:
tiddlywiki-data:

34
docker/entrypoint.sh Executable file
View file

@ -0,0 +1,34 @@
#!/bin/sh
set -e
WIKI_DIR="${TIDDLYWIKI_DATA_DIR:-/data}"
HOST="${TIDDLYWIKI_HOST:-0.0.0.0}"
PORT="${TIDDLYWIKI_PORT:-8080}"
# Initialize wiki on first run
if [ ! -f "${WIKI_DIR}/tiddlywiki.info" ]; then
echo "Initializing new wiki at ${WIKI_DIR} ..."
tiddlywiki "${WIKI_DIR}" --init server
fi
# Build --listen argument list
LISTEN_ARGS="host=${HOST} port=${PORT}"
if [ -n "${TIDDLYWIKI_USERNAME}" ]; then
LISTEN_ARGS="${LISTEN_ARGS} username=${TIDDLYWIKI_USERNAME}"
fi
if [ -n "${TIDDLYWIKI_PASSWORD}" ]; then
LISTEN_ARGS="${LISTEN_ARGS} password=${TIDDLYWIKI_PASSWORD}"
fi
if [ -n "${TIDDLYWIKI_READERS}" ]; then
LISTEN_ARGS="${LISTEN_ARGS} readers=${TIDDLYWIKI_READERS}"
fi
if [ -n "${TIDDLYWIKI_WRITERS}" ]; then
LISTEN_ARGS="${LISTEN_ARGS} writers=${TIDDLYWIKI_WRITERS}"
fi
echo "Starting TiddlyWiki on ${HOST}:${PORT} ..."
exec tiddlywiki "${WIKI_DIR}" --listen ${LISTEN_ARGS}

View file

@ -0,0 +1,132 @@
title: TiddlyWiki with Docker
tags: [[TiddlyWiki on Node.js]] Docker
type: text/vnd.tiddlywiki
This page describes how to run [[TiddlyWiki on Node.js]] using Docker.
!! Quick Start
The simplest way to run TiddlyWiki in Docker is with `docker run`:
```
docker run --init -p 127.0.0.1:8080:8080 \
-v "$(pwd)/data:/data" \
tiddlywiki/tiddlywiki
```
Then open http://localhost:8080 in your browser. Wiki data is persisted in a `data` folder in the current directory.
!! Mapping a Host Directory
By default Docker uses a named volume to store wiki data. To use a host directory (bind mount) instead:
```
mkdir -p ./my-wiki
docker run --init -p 127.0.0.1:8080:8080 \
-v "$(pwd)/my-wiki:/data" \
--user "$(id -u):$(id -g)" \
tiddlywiki/tiddlywiki
```
The container runs as UID/GID `1000` by default, which matches the first non-root user on most Linux systems. If your host user has a different UID, pass `--user "$(id -u):$(id -g)"` to override.
With Docker Compose, replace the named volume with a bind mount:
```yaml
volumes:
- ./my-wiki:/data
# Only needed if your host UID is not 1000:
# user: "${UID}:${GID}"
```
!!! macOS
On macOS the first user has UID `501`, not `1000`. Always pass `--user` explicitly:
```
mkdir -p ./my-wiki
docker run --init -p 127.0.0.1:8080:8080 \
-v "$(pwd)/my-wiki:/data" \
--user "$(id -u):$(id -g)" \
tiddlywiki/tiddlywiki
```
Or in `docker-compose.yml`:
```yaml
volumes:
- ./my-wiki:/data
user: "${UID}:${GID}"
```
Note: Docker Desktop on macOS handles volume permissions through a file-sharing layer (gRPC FUSE or VirtioFS). If you encounter permission errors despite setting `--user`, check that the host directory is included in Docker Desktop → Settings → Resources → File Sharing.
!! Authentication
For internet-facing instances, always set a username and password:
```
docker run --init -p 127.0.0.1:8080:8080 \
-v "$(pwd)/data:/data" \
-e TIDDLYWIKI_USERNAME=admin \
-e TIDDLYWIKI_PASSWORD=changeme \
tiddlywiki/tiddlywiki
```
!! Using Docker Compose
Save the following as `docker-compose.yml` and run `docker compose up -d`:
```yaml
services:
tiddlywiki:
image: tiddlywiki/tiddlywiki:latest
init: true
ports:
- "127.0.0.1:8080:8080"
volumes:
- tiddlywiki-data:/data
environment:
TIDDLYWIKI_USERNAME: admin
TIDDLYWIKI_PASSWORD: changeme
TIDDLYWIKI_READERS: "(anon)"
TIDDLYWIKI_WRITERS: "(authenticated)"
restart: unless-stopped
volumes:
tiddlywiki-data:
```
!! Environment Variables
|!Variable |!Default |!Description |
|`TIDDLYWIKI_DATA_DIR` |`/data` |Path inside the container where wiki files are stored |
|`TIDDLYWIKI_HOST` |`0.0.0.0` |Host address to bind to |
|`TIDDLYWIKI_PORT` |`8080` |Port to listen on |
|`TIDDLYWIKI_USERNAME` |''(empty)'' |HTTP Basic Auth username |
|`TIDDLYWIKI_PASSWORD` |''(empty)'' |HTTP Basic Auth password |
|`TIDDLYWIKI_READERS` |`(anon)` |Who can read: `(anon)`, `(authenticated)`, or a specific username |
|`TIDDLYWIKI_WRITERS` |`(authenticated)` |Who can write: `(anon)`, `(authenticated)`, or a specific username |
!! Building from Source
To build the image yourself from the TiddlyWiki5 source:
```
git clone https://github.com/TiddlyWiki/TiddlyWiki5.git
cd TiddlyWiki5
docker build --build-arg NODE_LTS_VERSION=22 -t tiddlywiki .
```
!! Security Notes
* The container runs as an unprivileged `tiddlywiki` user (not root).
* Bind the host port to `127.0.0.1` (as shown above) unless you intend to expose TiddlyWiki directly to a network.
* For production use, place a reverse proxy (e.g. nginx or Caddy) with TLS in front of the container.
!! See Also
* [[TiddlyWiki on Node.js]]
* [[WebServer API: Authenticating with the WebServer]]
* [[WebServer Parameter: readers]]
* [[WebServer Parameter: writers]]