This is a Docker image for hosting your own 5eTools instance. It is based on the Apache httpd Alpine image and follows the official 5eTools installation guidance. The container builds the 5eTools project at runtime using npm, ensuring you always get a properly built version with the latest updates. This image is built from this GitHub repository.
This project is based on the original repository: https://github.com/Jafner/5etools-docker.
This fork provides several improvements over the original implementation:
| Metric | This Image (Alpine 3.23) | Original (Debian 12) | Improvement |
|---|---|---|---|
| Image Size | 152 MB | 279 MB | 1.8x smaller |
| Installed Packages | 62 | 154 | 2.5x fewer |
| Total CVEs | 2 | 3525 | substantially fewer |
| Critical CVEs | 0 | 14 | 100% elimination |
| High CVEs | 0 | 912 | 100% elimination |
| Medium CVEs | 1 | 2554 | substantially fewer |
| Low CVEs | 1 | 45 | substantially fewer |
Measured on February 15, 2026 using local docker build, docker image inspect, apk info, and Trivy (--ignore-unfixed). Results vary over time as base images and vulnerability DBs change.
- Base Image: Alpine Linux 3.23 (latest) vs Debian 12 (Bookworm)
- Runtime Build: Builds project at startup following official 5eTools installation flow (npm ci, build:sw:prod, optional SEO)
- Zero Critical/High CVEs: All critical and high-severity vulnerabilities eliminated ✅
- Smaller Attack Surface: 2.5x fewer packages means significantly fewer potential vulnerabilities
- Active Maintenance: Always uses latest Alpine version with most recent security patches
- Automated Security: Dependabot + Trivy scanning on every build
- Security Hardening: Removes git metadata (.git, .github) and build artifacts after installation
- Smart Updates: Compares local
package.jsonversion with remote release tag and rebuilds only when different - Enhanced Security Model: See Security section below
Alpine Linux is purpose-built for containers with minimal bloat. The smaller footprint means:
- Faster image pulls and container startup
- Reduced disk space usage
- Fewer packages to patch and maintain
- Smaller attack surface for security vulnerabilities
This project implements comprehensive automated security monitoring:
Continuous Integration/Deployment:
- Multi-architecture builds (linux/amd64, linux/arm64) on pushes/PRs to
mainexcept ignored paths (.github/**,docker-compose.yml,*.md,.gitignore,.dockerignore) - Automated builds pushed to both GitHub Container Registry and Docker Hub
- Docker build caching for faster iterations
Automated Vulnerability Scanning:
- Trivy scanner runs on every push to main branch
- Scans for OS and library vulnerabilities (Critical, High, Medium severity)
- Results automatically uploaded to GitHub Security tab as SARIF reports
- Build artifacts are scanned using image digest for accuracy
- Secondary summary report highlights Critical/High vulnerabilities
Dependency Management:
- Dependabot monitors base images and GitHub Actions daily/weekly
- Automatic pull requests for security updates
- Keeps dependencies current with minimal manual intervention
Security Visibility: View real-time security scan results in your repository's Security → Code scanning tab. All vulnerability findings are tracked and can trigger alerts for new CVEs.
This implementation follows Docker security best practices:
- Apache worker processes run as non-root (PUID/PGID specified user)
- Apache master process runs as root only for initial setup (standard Apache practice)
- All web content files owned by non-root user (PUID:PGID)
- Privilege separation via Apache's native User/Group directives
- Downloaded files are owned by the user specified via
PUID/PGIDenvironment variables - Apache logs directory properly permissioned for non-root access
- Git operations complete before ownership transfer to ensure consistent permissions
- Minimal package installation (git, jq, su-exec, npm, curl)
- Build artifact cleanup: Removes .git, .github, node_modules after build to reduce attack surface
- Git cache isolation: Image repository .git stored in separate cache directory
.dockerignoreprevents sensitive files in build context- Healthcheck monitors container status
- Idempotent startup script handles container restarts gracefully
The original implementation runs Apache entirely as root, while this fork properly segregates privileges:
- Original: All Apache processes run as root
- This fork: Master as root (required), workers as PUID:PGID (least privilege)
This follows the principle of least privilege and reduces the impact of potential Apache vulnerabilities.
Below we talk about how to install and configure the container.
You can quick-start this image by running:
curl -o docker-compose.yml https://raw.githubusercontent.com/Sakujakira/5etools-docker/refs/heads/main/docker-compose.yml
docker-compose up -d && docker logs -f 5etools-dockerFirst startup takes 5-10 minutes as the container:
- Clones the 5eTools source repository from GitHub
- Installs npm dependencies
- Builds the project (service worker, optional SEO optimization)
- Cleans up build artifacts
Subsequent restarts are much faster as the container detects the existing version and only rebuilds if an update is available.
The site will be accessible at localhost:8080 once the build completes. Monitor progress with docker logs -f 5etools-docker.
Files persist in Docker-managed named volumes. Use docker-compose down -v to remove volumes and start fresh.
By default, the container uses Docker-managed named volumes for data persistence:
htdocs: Built 5eTools fileslogs: Apache logsgit-cache: Git repository cache for faster image updates
This is the recommended approach as it:
- Persists data between container restarts
- Works seamlessly across different host systems
- Keeps volumes separate from your file system
To start fresh, remove volumes with: docker-compose down -v
If you need direct file access (e.g., for adding homebrew), use host-mounted volumes:
volumes:
- ~/5etools-docker/htdocs:/usr/local/apache2/htdocs
- ~/5etools-docker/logs:/usr/local/apache2/logs
- ~/5etools-docker/git-cache:/root/.cache/gitNote: With host volumes, you can access built files directly, but you'll need to handle file permissions correctly (use PUID/PGID environment variables).
To use pre-created Docker volumes:
docker volume create 5etools-htdocs
docker volume create 5etools-logs
docker volume create 5etools-git-cacheThen in docker-compose.yml:
volumes:
htdocs:
external: true
name: 5etools-htdocs
logs:
external: true
name: 5etools-logs
git-cache:
external: true
name: 5etools-git-cacheThe image uses environment variables for configuration. By default, it automatically downloads and builds the latest 5eTools version from GitHub. All variables are optional with sensible defaults.
Controls whether to download image files alongside the main content.
environment:
- IMG=TRUE # Download images (~2GB, takes longer)
- IMG=FALSE # Skip images (default, faster startup)TRUE: Clones image repository from https://github.com/5etools-mirror-3/5etools-imgFALSE: Main content only, no artwork/maps (faster, smaller)
The container uses a git cache to speed up subsequent image updates.
Skip GitHub updates and use existing built files.
environment:
- OFFLINE_MODE=TRUEUseful for air-gapped environments. Requires pre-built files in the volume. Container exits if no local version exists.
In OFFLINE_MODE=TRUE, the container does not call the GitHub API and does not attempt repository updates.
Build SEO-optimized version of the site.
environment:
- SEO_OPTION=TRUERuns npm run build:seo after the standard build. See 5eTools Install Guide for details.
Force npm to fix vulnerabilities that may introduce breaking changes.
environment:
- NPM_AUDIT_FORCE_FIX=TRUEnpm audit fix without --force flag for safer updates.
Control file ownership and Apache worker process user/group.
environment:
- PUID=1001 # User ID
- PGID=1001 # Group IDThe container dynamically creates a user/group with specified IDs and:
- Sets ownership of all built files to PUID:PGID
- Configures Apache worker processes to run as PUID:PGID
- Ensures proper file permissions for host-mounted volumes
Why this matters: Match your host user's UID/GID to access files without permission issues when using host-mounted volumes.
Do not expose this container directly to the public internet.
Recommended deployment pattern:
- Run this container on an internal Docker network
- Terminate TLS at a reverse proxy
- Publish only the reverse proxy ports (80/443)
This project recommends using one of these reverse proxies:
linuxserver/swagCaddyTraefik
Examples for these proxy options are planned in the repository examples section.
- Centralized TLS certificate management and HTTPS redirects
- Cleaner security controls (headers, access rules, optional auth)
- Better separation between application container and internet-facing edge
To use auto-loading homebrew, you will need to use a host directory mapping (not named volumes). Update your docker-compose.yml to use host-mounted volumes as described in the Volume Mapping section above.
- Start the container and wait for the build to complete. Monitor progress with
docker logs -f 5etools-docker(first build takes 5-10 minutes). - Once running, assuming you are using the mapping
~/5etools-docker/htdocs:/usr/local/apache2/htdocs, place your homebrew JSON files into the~/5etools-docker/htdocs/homebrew/folder. - Add the filenames to the
~/5etools-docker/htdocs/homebrew/index.jsonfile. For example, if your homebrew folder contains:
index.json
'Jafner; JafnerBrew Campaigns.json'
'Jafner; JafnerBrew Collection.json'
'Jafner; Legendary Tomes of Knowledge.json'
'KibblesTasty; Artificer (Revised).json'
Then your index.json should look like:
{
"readme": [
"NOTE: This feature is designed for use in user-hosted copies of the site, and not for integrating \"official\" 5etools content.",
"This file contains an index for other homebrew files, which should be placed in the same directory.",
"For example, add \"My Homebrew.json\" to the \"toImport\" array below, and have a valid JSON homebrew file in this (\"homebrew/\") directory."
],
"toImport": [
"Jafner; JafnerBrew Collection.json",
"Jafner; JafnerBrew Campaigns.json",
"Jafner; Legendary Tomes of Knowledge.json",
"KibblesTasty; Artificer (Revised).json"
]
}Note: The IS_DEPLOYED flag in js/utils.js is automatically set to the deployed version number during the build process (properly quoted as a string literal), enabling homebrew support. You don't need to manually edit this file.
Note the commas after each entry except the last in each array. See the 5eTools Install Guide for more information.
To reproduce the comparison table on your machine, run:
# Build current image from this repository
docker build -t 5etools-doc-review:local .
# Current image metrics
docker image inspect 5etools-doc-review:local --format '{{.Size}}'
docker run --rm 5etools-doc-review:local sh -lc "apk info | wc -l"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest \
image --ignore-unfixed --severity CRITICAL,HIGH,MEDIUM,LOW --format table 5etools-doc-review:local
# Original image metrics
docker pull jafner/5etools-docker:latest
docker image inspect jafner/5etools-docker:latest --format '{{.Size}}'
docker run --rm jafner/5etools-docker:latest sh -lc "dpkg-query -W -f='${binary:Package}\n' | wc -l"
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest \
image --ignore-unfixed --severity CRITICAL,HIGH,MEDIUM,LOW --format table jafner/5etools-docker:latestUse one scanner/toolchain consistently for both images. CVE counts can change daily as vulnerability databases and base image tags are updated.
This project was developed with assistance from Claude (Anthropic's AI assistant) for:
- Documentation writing and formatting
- Code reviews and security analysis
- Identifying potential issues and suggesting improvements
However, all architectural decisions, implementation choices, and code changes were made by the repository maintainer. The AI served as a development tool for analysis and documentation, not as the primary author of the codebase.
The core improvements (Alpine migration, security enhancements, git operation fixes, and privilege separation) were designed and implemented by human developers.