A low memory, <10MB Go web server that serves markdown files converted to HTML.
- π Automatic Markdown to HTML conversion using the
gomarkdownlibrary - π¨ Clean, responsive HTML template with modern CSS styling
- π File-based routing - serve
.mdfiles from thecontent/directory - π Clean URLs - access files with or without the
.mdextension - π¦ Static file serving for CSS, images, and other assets
- π³ Ultra-minimal Docker containers using scratch base image (no OS!)
- π‘οΈ Auto-generated sample content when content directory is empty
go-markdown-server/
βββ main.go # Main server application
βββ go.mod # Go module dependencies
βββ go.sum # Dependency checksums (generated)
βββ Dockerfile # Docker build configuration (scratch-based)
βββ LICENSE # MIT License
βββ README.md # This file
βββ content/ # Directory for markdown files
β βββ index.md # Sample homepage content
βββ static/ # Directory for static assets
βββ style.css # CSS styling for HTML output
- Go 1.21 or later
- Docker (optional, for containerized deployment)
-
Install Go dependencies:
go mod tidy
-
Run the server locally:
go run main.go
-
Access the server: Open your browser to
http://localhost:8080
The server can be configured using environment variables:
PORT: Server port (default:8080)CONTENT_DIR: Directory containing markdown files (default:./content)HTTP_SECURITY_HEADERS: Enable/disable HTTP security headers (default:enable, set todisableto turn off)
Example:
PORT=3000 CONTENT_DIR=/path/to/markdown/files HTTP_SECURITY_HEADERS=disable go run main.goThe server includes comprehensive HTTP security headers by default to protect against common web vulnerabilities:
- X-Content-Type-Options: Prevents MIME type sniffing attacks
- X-XSS-Protection: Enables XSS filtering in browsers
- Referrer-Policy: Controls referrer information sharing
- X-Permitted-Cross-Domain-Policies: Blocks Flash/PDF cross-domain requests
- Content-Security-Policy: Comprehensive CSP that allows iframe embedding while maintaining security
Note: Security headers can be disabled by setting HTTP_SECURITY_HEADERS=disable if needed for compatibility with legacy systems.
The Docker deployment includes advanced security hardening:
- Minimal privileges: All Linux capabilities dropped except what's necessary
- Non-root execution: Runs as user
1001:1001inside the container - No privilege escalation:
no-new-privilegessecurity option enabled - Path traversal protection: Server validates all file paths to prevent directory traversal attacks
- Path sanitization: All URL paths are validated and sanitized
- File extension restrictions: Only serves
.mdand.cssfiles from the content directory - Directory containment: Server ensures all file access stays within the designated content directory
This Dockerfile uses a scratch base image, creating an extremely small container with just the Go binary and essential files - no operating system!
Benefits:
- Tiny size: ~10-15MB total (vs ~50MB+ with Alpine)
- Enhanced security: No shell, package manager, or OS vulnerabilities
- Fast startup: Minimal overhead
- Production-ready: Statically linked binary with all dependencies included
Build and run with Docker Compose:
docker-compose up --buildBuild and run in background:
docker-compose up --build -dRun development version:
docker-compose --profile dev up --buildBuild the image without running:
docker-compose buildStop services:
docker-compose downBuild and tag for publishing:
docker-compose build
docker tag mountainpass/go-markdown-server:latest mountainpass/go-markdown-server:v1.0.0Push to registry:
docker push mountainpass/go-markdown-server:latest
docker push mountainpass/go-markdown-server:v1.0.0For publishing images that work on both Mac (ARM64) and Linux (AMD64) architectures, use the dedicated multiplatform compose file:
Prerequisites:
# Login to Docker Hub (or your preferred registry)
docker loginBuild and publish multi-platform images:
# Build and push directly to registry (recommended)
docker-compose -f docker-compose.multiplatform.yml build --push
# Or build first, then push separately
docker-compose -f docker-compose.multiplatform.yml build
docker-compose -f docker-compose.multiplatform.yml pushSupported architectures:
linux/amd64- Standard Linux x86_64 systemslinux/arm64- Mac M1/M2 (Apple Silicon) and ARM-based Linux systems
Note: Multi-platform builds create manifest lists that automatically serve the correct architecture when pulled. The images are stored in the registry but won't appear in your local docker image ls output unless specifically loaded.
Pull and run from registry:
docker pull mountainpass/go-markdown-server:latest
docker run -p 8080:8080 mountainpass/go-markdown-server:latestdocker build -t mountainpass/go-markdown-server .docker run -p 8080:8080 mountainpass/go-markdown-serverdocker run -p 8080:8080 -v ./content:/content mountainpass/go-markdown-serverdocker run -p 3000:3000 -e PORT=3000 -e CONTENT_DIR=/content mountainpass/go-markdown-server-
Add markdown files to the
content/directory -
Access files via clean URLs:
http://localhost:8080/β servescontent/index.mdhttp://localhost:8080/aboutβ servescontent/about.mdhttp://localhost:8080/docs/setupβ servescontent/docs/setup.md
-
Static assets can be placed in the
static/directory and accessed via/static/URL path -
Auto-generated content: If the content directory is empty, a sample
index.mdis automatically created
- Headers (H1-H6)
- Lists (ordered and unordered)
- Code blocks with syntax highlighting
- Links and images
- Tables
- Blockquotes
- Horizontal rules
- Bold and italic text
- Automatic heading IDs for anchor links
To modify the server:
- Edit
main.gofor server logic changes - Edit
static/style.cssfor styling changes - Add sample content to
content/directory
The server automatically extracts page titles from the first H1 header (# Title) in each markdown file.
The scratch-based Docker image provides:
- No attack surface: No shell, package manager, or OS components
- Minimal size: Only contains the statically-linked Go binary and essential files
- Fast deployment: Smaller images mean faster pulls and starts
- Production security: No unnecessary system packages or vulnerabilities
Note: The scratch approach works because Go can compile to a statically-linked binary that includes all dependencies. This is ideal for microservices and containerized deployments.
This project is open source and available under the MIT License.