Problem: Lightweight community forums are often bloated with SaaS lock-ins, complex database dependencies, or heavy runtime frameworks.
Solution: A zero-dependency Go web forum utilizing strict Hexagonal Architecture (Ports & Adapters) boundaries with SQLite, running in lightweight Docker containers.
A lightweight, self-hosted community forum with zero external dependencies. Designed for developers who want full control over their discussion platform without proprietary SaaS lock-in.
A web forum built with Go using Hexagonal Architecture (Ports & Adapters) in a modular monolith.
- Language: Go 1.24+
- Database: SQLite (
mattn/go-sqlite3, CGO enabled) - Entry point:
cmd/forum/main.go - Composition root:
cmd/forum/wire/
auth— register/login/logout, session validation, one active session per useruser— user profile + user statspost— CRUD, categories, filtering, image uploadcomment— CRUD + ownership validationreaction— like/dislike toggles on posts/comments
moderationnotification
- config, database connection, migrator, HTTP server + middleware, logger, validator, upload, health checks
For implementation status details: docs/IMPLEMENTATION_ROADMAP.md.
Internal DB IDs are integers, but never expose sequential IDs in URLs, templates, JSON, or request context.
- Use
PublicIDexternally - Keep integer IDs internal to persistence and domain relations
Hexagonal (ports-and-adapters) Go monolith:
- Core domain: threads, posts, user auth
- Adapters: PostgreSQL, REST API, WebSocket
- No framework — standard library + gorilla/mux
graph TD
subgraph "Hexagonal Module"
D[Domain] --> P[Ports]
P --> A[Application]
A --> Ad[Adapters]
Ad -->|HTTP| Rest[API]
Ad -->|SQL| SQLite[(SQLite)]
end
Each module follows this strict structure:
internal/modules/{module}/
├── domain/ # entities + business rules (stdlib only)
├── ports/ # input/output interfaces
├── application/ # use-case orchestration
└── adapters/ # HTTP handlers + SQLite repositories
Dependency direction:
adapters-> can importapplication/ports/domainapplication-> can importports/domainports-> can importdomaindomain-> no project-layer imports
Detailed reference: docs/ARCHITECTURE.md.
New contributors should start with the docs/guides/ONBOARDING_GUIDE.md.
- Go 1.24+
- CGO toolchain (required by SQLite driver)
sqlite3CLI (for seeding scripts)
make goThe app starts on:
- HTTP:
http://localhost:8080 - HTTPS:
https://localhost:8443(only if cert/key files exist)
Some devcontainers do not include Docker Engine/CLI access. If make up prints a Docker/Compose availability error, run Docker commands from your host terminal instead.
make seedThis runs migrations first, then loads seed data.
make up # start
make down # stopmake up now auto-detects docker compose (preferred) and falls back to docker-compose when available.
Compose exposes 8080 (HTTP) and 8443 (HTTPS) and mounts ./data, ./static/uploads, and ./certs.
# Build the image
docker build -t forum .
# First run — create a named container with volumes for data persistence
docker run -d --name forum -p 8080:8080 \
-v forum-data:/app/data \
-v forum-uploads:/app/static/uploads \
forum
# Stop
docker stop forum
# Start again (reuses the same container and data)
docker start forum
# View logs
docker logs -f forum
# Remove the container (volumes are preserved)
docker rm forumThe app binds to 0.0.0.0 by default so it is accessible from outside the container without extra flags.
The container entrypoint also fixes mounted volume permissions at startup before dropping to appuser, preventing startup crashes that can appear as browser connection resets.
If you run docker run -p 8080:8080 forum repeatedly without --name, Docker will create a new container each time. Use --name forum + docker start forum to reuse the same container.
If you don't have sqlite3 installed locally, you can run the seeding script via a disposable tools container:
docker run --rm -v "${PWD}:/workspace" -w /workspace alpine:3.20 sh -lc \
"apk add --no-cache bash sqlite openssl >/dev/null && DATABASE_PATH=/workspace/data/forum.db bash scripts/seed/seed.sh"make test # full suite (go + integration + script tests)
make test-go # go tests only
make test-script # e2e script tests only
make test-coveragemake go # run with go run
make build # build binary
make migrate # run SQL migrations
make seed # migrate + seed DB
make help # full target list- App entry:
cmd/forum/main.go - DI wiring:
cmd/forum/wire/ - Modules:
internal/modules/ - Shared platform:
internal/platform/ - SQL migrations:
migrations/ - Templates:
templates/ - Static files/uploads:
static/ - Tests:
tests/andscripts/tests/
All JSON endpoints are under /api.
Examples:
POST /api/auth/registerPOST /api/auth/loginGET /api/postsPOST /api/comments/posts/{post_id}POST /api/reactions
- Add
domainentities + validation + errors - Define interfaces in
ports/service.goandports/repository.go - Implement use cases in
application/service.go - Add adapters (
http_handler*.go,sqlite_repository.go) - Register in
cmd/forum/wire/{repositories,services,handlers}.goand routes inapp.go - Add migration:
migrations/NNN_module.sql
Use internal/modules/auth/ as the reference implementation.
- ertval.github.io — Portfolio & CV
- two-tier-safe-ai-gate — Safe AI execution model (Go + Inngest + Omnigent)
- keel-multi-agent-pipeline — Multi-agent maritime intelligence (Python + LangGraph)
- social-network — Go vertical-slices full-stack monolith (Next.js)
- make-your-game — Pure JS ECS game engine
- real-time-forum — Go + Vanilla JS real-time WebSocket SPA
- forum — Go hexagonal architecture monolith (zero-dependency)