Self-hosted e-ink device management server built for the homelab community. Works with TRMNL devices (supports firmware 1.7.8) and any BYOD e-ink display. Design screens, create custom widgets with live data from your local network, and manage your displays from a modern web interface.
Inker is heading in its own direction — focusing on homelab integrations like server monitoring, smart home dashboards, network stats, and self-hosted service displays. TRMNL device compatibility is maintained, but the plugin ecosystem will be Inker-native.
- Screen Designer — Drag & drop widget placement, snap guides, freehand drawing, auto-fit zoom for any resolution
- Built-in Widgets — Clock, date, text, weather, countdown, days until, QR code, image, GitHub stars, battery, WiFi, device info
- Custom Widgets — Connect to any JSON API or RSS feed (including local network sources), JavaScript transformations, grid layouts
- Plugins — Coming soon — homelab-native integrations for server monitoring, smart home, network stats
- Playlists — Rotate multiple screens on devices automatically
- Device Management — Auto-provisioning, firmware 1.7.8 support, real-time status, logs
- BYOD Support — Register any e-ink device manually with custom screen resolution
- No external infra — Runs locally with Bun and SQLite, no database or cache service required
| Screen Designer | Devices | Screens |
|---|---|---|
![]() |
![]() |
![]() |
| List of sources | Custom Data Sources | Custom Widgets |
|---|---|---|
![]() |
![]() |
![]() |
bun run install:all
bun run devOpen http://localhost:3337 and log in with PIN 1111.
The dev launcher creates the SQLite database at backend/data/inker.db, syncs the Prisma schema, seeds built-in models/templates, then starts:
- public app:
http://localhost:3337 - internal backend:
http://localhost:3338
In dev, Vite owns port 3337 and proxies /api plus /uploads to the backend. TRMNL devices should use the same public URL as the browser, for example http://de-unit-2506.local:3337.
For single-port mode without Vite HMR:
bun run dev:singleOpen http://localhost:3337. This builds the frontend and serves it from the Nest backend.
Inker includes a local stdio MCP server for agent-driven CRUD over screen designs, widgets, data sources, custom widgets, and playlists:
bun run mcpExample Codex/Claude-style command:
{
"command": "bun",
"args": ["run", "mcp"],
"cwd": "/path/to/inker"
}The server uses the same SQLite default as the app (backend/data/inker.db) unless DATABASE_URL is set.
If your Mac cannot expose port 3337 to the LAN, but a Raspberry Pi can, bring up the temporary SSH proxy:
bun run proxy:rpiBy default it exposes http://birdnet-pi.local:3337 and forwards traffic back to the local app on 127.0.0.1:3337. Stop it with:
bun run proxy:rpi:down# docker-compose.yml
services:
inker:
image: wojooo/inker:latest
container_name: inker
restart: unless-stopped
ports:
- "80:3337"
volumes:
- sqlite_data:/app/data
- uploads_data:/app/uploads
environment:
TZ: UTC
ADMIN_PIN: "1111" # Quotes required — YAML strips leading zeros without them
DATABASE_URL: "file:../data/inker.db"
volumes:
sqlite_data:
uploads_data:docker compose up -dOpen http://your-server-ip and log in with PIN 1111.
| Variable | Description | Default |
|---|---|---|
ADMIN_PIN |
Login PIN | 1111 |
TZ |
Timezone for widgets | UTC |
HOST |
Backend listen host | 127.0.0.1 |
PORT |
Backend port | 3337 |
VITE_HOST |
Frontend dev listen host | 127.0.0.1 |
VITE_PORT |
Frontend dev port | 3337 |
VITE_BACKEND_PORT |
Frontend dev server backend target | 3338 |
DATABASE_URL |
SQLite database URL | file:../data/inker.db |
CORS_ORIGINS |
Allowed CORS origins (comma-separated, or * for all) |
same-origin |
Pass with -e:
docker run -d \
--name inker \
--restart unless-stopped \
-p 80:3337 \
-e ADMIN_PIN="1111" \
-e TZ=Europe/Warsaw \
-e DATABASE_URL="file:../data/inker.db" \
-v inker_data:/app/data \
-v inker_uploads:/app/uploads \
inker:latestgit clone https://github.com/wojo-o/inker.git
cd inker
bun run install:all
bun run devbun run install:all
bun run db:initAll data (screens, devices, playlists, settings) is preserved in backend/data/inker.db. Uploads are stored under backend/uploads/.
If something isn't working after an update or on first run, reset local runtime state and start fresh:
rm -rf backend/data backend/uploads
bun run devNote: This removes all data (database, uploads). Only use on a fresh install or when you don't mind losing data.
cd backend && bun test # 395 tests
cd frontend && bun run test # 19 testsSource Available — see LICENSE for details.







