From 25f0bf2cf059b3ecffe20fdb5bc5b1f0f400e262 Mon Sep 17 00:00:00 2001 From: himmel Date: Wed, 18 Mar 2026 09:49:44 +0000 Subject: [PATCH] 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 --- .dockerignore | 26 ++++ .github/workflows/docker-publish.yml | 76 ++++++++++ Dockerfile | 47 +++++++ bin/docker-publish.sh | 31 ++++ docker-compose.yml | 38 +++++ docker/entrypoint.sh | 34 +++++ .../howtos/TiddlyWiki with Docker.tid | 132 ++++++++++++++++++ 7 files changed, 384 insertions(+) create mode 100644 .dockerignore create mode 100644 .github/workflows/docker-publish.yml create mode 100644 Dockerfile create mode 100755 bin/docker-publish.sh create mode 100644 docker-compose.yml create mode 100755 docker/entrypoint.sh create mode 100644 editions/tw5.com/tiddlers/howtos/TiddlyWiki with Docker.tid diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..102ad0f183 --- /dev/null +++ b/.dockerignore @@ -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 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml new file mode 100644 index 0000000000..3f83a028bf --- /dev/null +++ b/.github/workflows/docker-publish.yml @@ -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 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..c71eb51999 --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/bin/docker-publish.sh b/bin/docker-publish.sh new file mode 100755 index 0000000000..f782c92de3 --- /dev/null +++ b/bin/docker-publish.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash +# Publish an official TiddlyWiki Docker image to Docker Hub. +# +# Usage: +# ./bin/docker-publish.sh [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 [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}" diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..466b4713f4 --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000000..f8b9189181 --- /dev/null +++ b/docker/entrypoint.sh @@ -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} diff --git a/editions/tw5.com/tiddlers/howtos/TiddlyWiki with Docker.tid b/editions/tw5.com/tiddlers/howtos/TiddlyWiki with Docker.tid new file mode 100644 index 0000000000..5a9c8b7d55 --- /dev/null +++ b/editions/tw5.com/tiddlers/howtos/TiddlyWiki with Docker.tid @@ -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]]