aRustyDev / aRustyDev/mdbook-htmx
docs(mdbook-htmx): Docker Compose Local Development
- Dominant language
- Rust
- Stars
- 0
- Forks
- 1
- PR merge metrics
- No merged PRs in 30d
Description
# Docker Compose Local Development
Run mdbook-htmx locally with Docker Compose for development and testing.
## Implementation
The Docker Compose configuration lives in the mdbook-htmx repository:
```
mdbook-htmx/docker/
├── compose.yml # Base configuration
├── compose.auth.yml # +OAuth2 proxy (Phase 3)
├── compose.search.yml # +Meilisearch (Phase 4)
├── compose.prod.yml # +Caching, metrics (Phase 5)
├── nginx.conf # NGINX with HTMX routing
├── Dockerfile.build # Multi-stage build image
└── .env.example # Environment template
```
See [ADR-0016](../adr/0016-implementation-phasing-strategy.md) for phase-by-phase additions.
## Overview
This setup includes:
- **NGINX**: Serving documentation with HTMX fragment routing
- **Meilisearch**: Full-text search (optional, Phase 4+)
- **OAuth2 Proxy**: Authentication (optional, Phase 3+)
- **Live reload**: Watch for changes
## Basic Setup
### docker-compose.yml
```yaml
version: "3.8"
services:
docs:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./book:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/healthz"]
interval: 10s
timeout: 5s
retries: 3
# Live rebuild (optional)
builder:
image: rust:latest
volumes:
- .:/docs
- cargo-cache:/usr/local/cargo/registry
working_dir: /docs
command: >
bash -c "
cargo install mdbook mdbook-htmx &&
mdbook watch --open
"
profiles:
- dev
volumes:
cargo-cache:
```
### nginx.conf
```nginx
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
gzip on;
gzip_types text/plain text/css application/json application/javascript;
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# HTMX requests
location / {
try_files $uri $uri/ /index.html;
# CORS for local development
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
add_header Access-Control-Allow-Headers "Content-Type, HX-Request";
}
# Health check
location /healthz {
return 200 "OK\n";
add_header Content-Type text/plain;
}
}
}
```
## With Meilisearch
### docker-compose.yml
```yaml
version: "3.8"
services:
docs:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./book:/usr/share/nginx/html:ro
- ./nginx-with-search.conf:/etc/nginx/nginx.conf:ro
depends_on:
meilisearch:
condition: service_healthy
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost/healthz"]
interval: 10s
timeout: 5s
retries: 3
meilisearch:
image: getmeili/meilisearch:v1.6
ports:
- "7700:7700"
environment:
MEILI_ENV: development
MEILI_MASTER_KEY: devkey123
MEILI_NO_ANALYTICS: "true"
volumes:
- meili-data:/meili_data
healthcheck:
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7700/health"]
interval: 10s
timeout: 5s
retries: 3
# Index documentation on startup
indexer:
image: python:3.11-slim
volumes:
- ./book:/docs:ro
- ./scripts:/scripts:ro
environment:
MEILI_HOST: http://meilisearch:7700
MEILI_MASTER_KEY: devkey123
command: >
bash -c "
pip install meilisearch &&
python /scripts/index.py
"
depends_on:
meilisearch:
condition: service_healthy
profiles:
- index
volumes:
meili-data:
```
### nginx-with-search.conf
```nginx
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
upstream meilisearch {
server meilisearch:7700;
}
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# Static files
location / {
try_files $uri $uri/ /index.html;
}
# Proxy search to Meilisearch
location /search {
proxy_pass http://meilisearch/indexes/docs/search;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Authorization "Bearer devkey123";
# CORS
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS" always;
add_header Access-Control-Allow-Headers "Content-Type, Authorization" always;
if ($request_method = OPTIONS) {
return 204;
}
}
location /healthz {
return 200 "OK\n";
}
}
}
```
### scripts/index.py
```python
#!/usr/bin/env python3
"""Index documentation to Meilisearch."""
import json
import os
import hashlib
from pathlib import Path
import meilisearch
import time
MEILI_HOST = os.environ.get('MEILI_HOST', 'http://localhost:7700')
MEILI_KEY = os.environ.get('MEILI_MASTER_KEY', 'devkey123')
DOCS_PATH = os.environ.get('DOCS_PATH', '/docs')
def main():
# Wait for Meilisearch
client = meilisearch.Client(MEILI_HOST, MEILI_KEY)
for _ in range(30):
try:
client.health()
break
except:
time.sleep(1)
else:
raise Exception("Meilisearch not ready")
# Load search index
search_index = Path(DOCS_PATH) / 'search-index.json'
if not search_index.exists():
print("No search-index.json found, skipping indexing")
return
with open(search_index) as f:
data = json.load(f)
# Transform documents
documents = []
for page in data.get('pages', []):
documents.append({
'id': hashlib.md5(page['path'].encode()).hexdigest(),
'path': page['path'],
'title': page['title'],
'content': page.get('content', ''),
'headings': page.get('headings', []),
})
# Create/update index
index = client.index('docs')
index.update_settings({
'searchableAttributes': ['title', 'headings', 'content'],
'displayedAttributes': ['path', 'title', 'headings'],
})
task = index.add_documents(documents)
client.wait_for_task(task.task_uid)
print(f"Indexed {len(documents)} documents")
if __name__ == '__main__':
main()
```
## With Live Reload
### docker-compose.yml
```yaml
version: "3.8"
services:
docs:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./book:/usr/share/nginx/html:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
# Watch and rebuild
watcher:
build:
context: .
dockerfile: Dockerfile.dev
volumes:
- .:/docs
- cargo-cache:/usr/local/cargo/registry
working_dir: /docs
command: mdbook watch
environment:
RUST_LOG: info
# Browser sync (optional)
browsersync:
image: ustwo/browser-sync
ports:
- "3000:3000"
- "3001:3001"
command: start --proxy "docs:80" --files "book/**/*"
depends_on:
- docs
volumes:
cargo-cache:
```
### Dockerfile.dev
```dockerfile
FROM rust:latest
# Install mdbook and plugins
RUN cargo install mdbook mdbook-htmx
# Install watchexec for better file watching
RUN cargo install watchexec-cli
WORKDIR /docs
```
## With Authentication (OAuth2 Proxy)
### docker-compose.yml
```yaml
version: "3.8"
services:
docs:
image: nginx:alpine
volumes:
- ./book:/usr/share/nginx/html:ro
- ./nginx-auth.conf:/etc/nginx/nginx.conf:ro
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.5.1
ports:
- "4180:4180"
environment:
OAUTH2_PROXY_PROVIDER: github
OAUTH2_PROXY_CLIENT_ID: ${GITHUB_CLIENT_ID}
OAUTH2_PROXY_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
OAUTH2_PROXY_COOKIE_SECRET: ${COOKIE_SECRET}
OAUTH2_PROXY_EMAIL_DOMAINS: "*"
OAUTH2_PROXY_UPSTREAMS: http://docs:80
OAUTH2_PROXY_HTTP_ADDRESS: 0.0.0.0:4180
OAUTH2_PROXY_REDIRECT_URL: http://localhost:4180/oauth2/callback
```
### .env
```bash
GITHUB_CLIENT_ID=your-client-id
GITHUB_CLIENT_SECRET=your-client-secret
COOKIE_SECRET=$(openssl rand -base64 32 | head -c 32)
```
## Make Commands
### Makefile
```makefile
.PHONY: build serve dev clean index
# Build documentation
build:
mdbook build
# Start docs server
serve:
docker-compose up -d docs
@echo "Docs available at http://localhost:8080"
# Development mode with live reload
dev:
docker-compose up docs watcher browsersync
# Start with search
search:
docker-compose up -d docs meilisearch
docker-compose run --rm indexer
# Rebuild search index
index:
docker-compose run --rm indexer
# Stop all services
stop:
docker-compose down
# Clean up
clean:
docker-compose down -v
rm -rf book/
# View logs
logs:
docker-compose logs -f
# Shell into docs container
shell:
docker-compose exec docs sh
```
## Environment Variables
### .env.example
```bash
# Meilisearch
MEILI_MASTER_KEY=devkey123
# OAuth (for authenticated docs)
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
COOKIE_SECRET=
# Build options
MDBOOK_BUILD=release
```
## Usage
```bash
# Start basic docs server
docker-compose up -d
# Build and serve with search
make search
# Development with live reload
make dev
# View logs
docker-compose logs -f docs
# Stop all
docker-compose down
```
## Port Reference
| Service | Port | Description |
|---------|------|-------------|
| docs (nginx) | 8080 | Documentation server |
| meilisearch | 7700 | Search API |
| browsersync | 3000 | Live reload proxy |
| browsersync | 3001 | BrowserSync UI |
| oauth2-proxy | 4180 | Auth proxy |
## Troubleshooting
### Container won't start
```bash
# Check logs
docker-compose logs docs
# Verify volumes
docker-compose config
# Rebuild
docker-compose build --no-cache
```
### Search not working
```bash
# Check Meilisearch health
curl http://localhost:7700/health
# Check index
curl http://localhost:7700/indexes/docs -H "Authorization: Bearer devkey123"
# Re-index
docker-compose run --rm indexer
```
### Permission issues
```bash
# Fix permissions on macOS/Linux
chmod -R 755 book/
```
## Next Steps
- Deploy to [Cloudflare Pages](./cf-pages-static.md)
- Set up [Kubernetes](./k8s-deployment.md)
- Configure [GitHub Pages](./github-pages.md)
Contributor guide
Assessment
This issue has not been assessed yet.