π Simple Mojolicious Markdown Documents Viewer
A tiny Mojolicious::Lite app that serves and renders a folder (and subfolders) of Markdown files.

π‘ Features
- Renders markdown (
.md/.markdown) files (recursively) from a docs folder (ignores everything else) - Nested filetree on the left hand side, great for navigation
- GitHub-style fenced code block support
- Syntax highlighting via
highlight.js - Pluggable Markdown backends (first found wins):
Text::MultiMarkdown(recommended)Text::MarkdownMarkdown::Tiny- fallback to
<pre>
- Safe path resolution prevents path traversal
- A fun cyberpunk theme (with theme support coming at some point)
- Code written by someone with no Mojolicious experience, but lots of Perl experience
β‘ Quickstart
Let's just get it running!
π¦ Prerequisites
# Setup Mojolicious (see URL above for more details), for example
cpanm Mojolicious
# Install a Markdown Renderer, for example
cpanm Text::MultiMarkdown
# Or via apt, for example
apt install libmojolicious-perl libtext-multimarkdown-perl
π’ Run Mojdoc
# Clone the app
git clone 'https://github.com/justinnamilee/mojdoc.git'
# Create the documents directory and fill it with Markdown files
cd mojdoc && mkdir -p private/dox
# Run in dev with hot-reload
morbo mojdoc
π§© Deploy Methods
Below are some tested deployment methods that work well for small internal sites or lightweight self-hosting setups.
π Systemd (with Hypnotoad)
This is the most βproduction-styleβ method for persistent deployment on Linux servers.
- Runs via Hypnotoad, Mojoliciousβs built-in pre-forking HTTP server.
- Handles restarts and crash recovery automatically.
- Well-documented in the Mojolicious Cookbook.
Example steps:
- Copy or adapt the provided service file: mojdoc.example.service
- Update the
WorkingDirectorysetting to your Mojdoc folder. - Enable and start it:
cp mojdoc.example.service /etc/systemd/system/mojdoc.service
vim /etc/systemd/system/mojdoc.service
systemctl daemon-reload
systemctl enable --now mojdoc.service
Tip: Use
journalctl -u mojdoc -fto follow logs live.
This setup is ideal for "set it and forget it" servers or internal documentation sites behind a reverse proxy.
βοΈ PM2 (Node.js Process Manager)
If you already use PM2 to manage other apps, Mojdoc can fit right in.
- Run Hypnotoad in foreground mode, and PM2 will manage restarts and logs.
Example steps:
- Copy or adapt the provided ecosystem file: ecosystem.config.example.js
- Start it and save it:
cp ecosystem.config.example.js ecosystem.config.js
vim ecosystem.config.js
pm2 start ecosystem.config.js
pm2 save
Tip: Use
pm2 log mojdocto follow logs live. Assuming you haven't changed thenamefield.
This approach is lightweight and convenient for developers or mixed stacks.
π³ Docker / Docker Compose
A container image is available for quick deployment.
- Example compose file: docker-compose.example.yml
- Container packages live under GitHub Packages.
- Mount your Markdown docs and config to
/opt/mojdocinside the container.
Example minimal docker-compose.yml:
version: "3"
services:
mojdoc:
image: ghcr.io/justinnamilee/mojdoc:latest
container_name: mojdoc
ports:
- "3000:3000"
volumes:
- ./private/dox:/opt/mojdoc/private/dox
- ./mojdoc.conf:/opt/mojdoc/mojdoc.conf
Then just start it:
docker compose up -d
Note: This is the cleanest way to deploy on any host β minimal dependencies, easy upgrades.
π§ͺ Development (Morbo)
For development or testing:
morbo mojdoc
- Auto-reloads on file changes.
- Logs everything to the console.
- Perfect for previewing local documentation sets.
π§ Summary
| Method | Best For | Pros | Cons |
|---|---|---|---|
| Systemd | Servers | Robust, auto-restart, native | Needs root/system access |
| PM2 | Dev/staging | Simple restarts, cross-platform | Requires Node/PM2 |
| Docker | Portability | Zero setup, isolated | Slightly more moving parts |
| Morbo | Local dev | Instant reloads | Not for production |
Author's Note: Of the two deploys I'm personally using, one is PM2 and one is Systemd.
Β―\_(γ)_/Β―
π§ Configuration
Configure via a Config plugin file (i.e. mojdoc.conf) or environment variables. For Docker, PM2, or Systemd deploys it is strongly recommended to use a config file (Docker config should be mounted to /opt/mojdoc/mojdoc.conf).
πͺΆ Specific Settings for mojdoc
| Setting | ENV Var | Default | What it does |
|---|---|---|---|
badge |
MOJDOC_BADGE |
cyber-docs |
Label used by the template UI. |
dox |
MOJDOC_DOX |
private/dox |
Root folder to scan for docs. |
welcome |
MOJDOC_WELCOME |
public/welcome.md |
Welcome file rendered on /. |
logit |
MOJDOC_LOGIT |
0 |
Enable view GET logs with 1. |
π§° Useful Settings for Mojolicious
| Setting | ENV Var | Default | What it does |
|---|---|---|---|
listen |
MOJO_LISTEN |
http://*:3000 |
List of listen URLs for the built-in server. |
proxy |
MOJO_REVERSE_PROXY |
0 (auto-on if trusted_proxies set) |
Treat X-Forwarded-* headers as authoritative (behind a trusted proxy). |
trusted_proxies |
MOJO_TRUSTED_PROXIES |
empty | CIDR/IP list of proxies to trust for client IP. |
log_level |
MOJO_LOG_LEVEL |
trace (dev), info (otherwise) |
Forces logger level (trace/debug/info/warn/error/fatal). |
max_request_size |
MOJO_MAX_REQUEST_SIZE |
16 MiB | Caps total HTTP request size (body + params). |
keep_alive_timeout |
MOJO_KEEP_ALIVE_TIMEOUT |
5 | Seconds that idle connections may stay open. |
π Example mojdoc.conf File
{
"badge": "super-secret-dox",
"dox": "/var/www/secure/dox",
"welcome": "/var/www/secure/welcome.md",
"logit": 0,
"hypnotoad": {
"listen": "http://*:9009",
"proxy": 1,
"trusted_proxies": "127.0.0.1, ::1"
}
}
For more information on this topic see The Mojolicious Cookbook.
π£οΈ Routes
GET /Renders the welcome page and shows a list of matching files discovered underDOX.GET /view/*docRenders a specific Markdown file. From files within theDOXtree.GET /healthReturnsOK. Useful for health checks.
π€ Maintainers / Support
For issues or feature requests, see GitHub Issues.
ποΈ Directory Structure
.
βββ docker-compose.yml ## example compose file, it's in .gitignore
βββ docker-compose.example.yml ## useful to deploy with Docker
βββ Dockerfile
βββ ecosystem.config.js ## example pm2 config, it's in .gitignore
βββ ecosystem.config.example.js ## useful to deploy with PM2
βββ LICENSE
βββ Makefile.PL
βββ mojdoc ## main application
βββ mojdoc.conf ## example application config, it's in .gitignore
βββ mojdoc.service ## example service file, it's in .gitignore
βββ mojdoc.example.service ## useful to deploy with Systemd
βββ private ## default public folder, it's in .gitignore
β βββ dox ## default path to look for documents (MOJDOC_DOX)
β β βββ yourExample.md ## example file that would be publicly served
β βββ welcome.md ## your replacement welcome.md if you wanted (MOJDOC_WELCOME)
βββ public
β βββ css
β β βββ mojdoc.css
β βββ favicon.svg
β βββ welcome.md ## default welcome page
βββ README.md
βββ t ## a non-zero amount of testing
β βββ ...
βββ templates ## a non-zero amount of website
βββ dox.html.ep
βββ exception.html.ep
βββ layouts
β βββ mojdoc.html.ep
βββ nodox.html.ep
βββ not_found.html.ep
βββ sidebar.html.ep
Made with π in Perl!