Skip to content

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

ServiceTechnologyRole
caddyCaddy 2TLS termination, reverse proxy, automatic Let's Encrypt certificates
serverNode.js / Express / Apollo GraphQLCore application: API, shortlink redirects, MCP server
appnginx serving React SPADashboard web application for managing shortlinks (app.lnkify.io)
dbPostgreSQL 16 AlpinePersistent data storage for all application state
redisRedis 7 AlpineShared state for rate limiting, MCP sessions, and idempotency
docsnginx serving static VitePress buildThis 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)
  1. All requests enter through Caddy on port 443 (HTTPS).
  2. Caddy terminates TLS and routes the request based on the hostname and path.
  3. Dashboard routes on app.lnkify.io go to the app nginx container (port 8080); GraphQL API calls on the same hostname go to the server.
  4. API calls, GraphQL queries, LLM discovery, and shortlink redirects on lnkify.io go to the server container (port 4000).
  5. 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:

HostnamePurpose
lnkify.ioGraphQL API, LLM discovery, shortlink redirects
app.lnkify.ioDashboard SPA for managing shortlinks
mcp.lnkify.ioModel Context Protocol server for AI agent integrations
docs.lnkify.ioDocumentation 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 app network allows services to communicate by service name (e.g., server, db, redis).
  • The db and redis services include healthchecks — server depends on both with condition: service_healthy.
  • Only caddy publishes ports to the host (80 and 443).
  • All other services are internal and not directly reachable from outside Docker.

Volumes

VolumeMounted ToPurpose
caddy_datacaddyTLS certificates, OCSP staples, Let's Encrypt account keys
caddy_configcaddyCaddy runtime configuration state
db-datadbPostgreSQL data files — persists all application data
redis_dataredisRedis 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

Released under the MIT License.