Architecture
lnkify is built as a multi-service application orchestrated by Docker Compose. Each service runs in its own container, communicating over an internal Docker network.
Stack Overview
| Service | Technology | Role |
|---|---|---|
| caddy | Caddy 2 | TLS termination, reverse proxy, automatic Let's Encrypt certificates |
| server | Node.js / Express / Apollo GraphQL | Core application: API, shortlink redirects, MCP server |
| app | nginx serving React SPA | Dashboard web application for managing shortlinks (app.lnkify.io) |
| db | PostgreSQL 16 Alpine | Persistent data storage for all application state |
| redis | Redis 7 Alpine | Shared state for rate limiting, MCP sessions, and idempotency |
| docs | nginx serving static VitePress build | This documentation site |
Request Flow
Browser
│
▼
Caddy (:443)
│
├── app.lnkify.io /graphql* ──────► server (Express :4000)
│
├── app.lnkify.io /* ──────────────► app (nginx :8080)
│
├── lnkify.io /graphql*, /llms* ───► server (Express :4000)
│
├── lnkify.io /* (shortlink slugs) ─► server (Express :4000)
│
├── mcp.lnkify.io ─────────────────► server MCP endpoint (:4000/mcp)
│
└── docs.lnkify.io ────────────────► docs (nginx :8080)- All requests enter through Caddy on port 443 (HTTPS).
- Caddy terminates TLS and routes the request based on the hostname and path.
- Dashboard routes on
app.lnkify.iogo to the app nginx container (port 8080); GraphQL API calls on the same hostname go to the server. - API calls, GraphQL queries, LLM discovery, and shortlink redirects on
lnkify.iogo to the server container (port 4000). - Server communicates with PostgreSQL on the internal Docker network (not exposed to the host).
Subdomain Routing
lnkify uses four hostnames, each serving a distinct purpose:
| Hostname | Purpose |
|---|---|
lnkify.io | GraphQL API, LLM discovery, shortlink redirects |
app.lnkify.io | Dashboard SPA for managing shortlinks |
mcp.lnkify.io | Model Context Protocol server for AI agent integrations |
docs.lnkify.io | Documentation site (this VitePress build) |
All four hostnames should point to the same server IP address. Caddy inspects the Host header to route to the correct backend.
Caddy & Automatic TLS
Caddy is the first service contacted by browsers. It handles:
- TLS termination — automatically obtains and renews Let's Encrypt certificates for all configured hostnames.
- HTTP→HTTPS redirect — all port 80 traffic is redirected to 443.
- Reverse proxying — routes requests to the appropriate backend service based on hostname and path.
Certificates are stored in the caddy_data Docker volume, persisting across container restarts and recreations.
Docker Compose Orchestration
All services are defined in a single docker-compose.yml file at the repository root. Key aspects of the orchestration:
- An internal
appnetwork allows services to communicate by service name (e.g.,server,db,redis). - The
dbandredisservices include healthchecks —serverdepends on both withcondition: service_healthy. - Only
caddypublishes ports to the host (80 and 443). - All other services are internal and not directly reachable from outside Docker.
Volumes
| Volume | Mounted To | Purpose |
|---|---|---|
caddy_data | caddy | TLS certificates, OCSP staples, Let's Encrypt account keys |
caddy_config | caddy | Caddy runtime configuration state |
db-data | db | PostgreSQL data files — persists all application data |
redis_data | redis | Redis persistent data (RDB/AOF snapshots) |
The Docs Service
The documentation site is a standalone nginx container serving the static VitePress build output. It is included in the Docker Compose stack so that self-hosters get a full copy of the documentation alongside their instance. It is accessed via docs.lnkify.io through the same Caddy reverse proxy.
Next: Quick Start