Self-hosted webcomic reader with panel-by-panel zoom. Next.js + SQLite, single-admin CMS.
486
A self-hosted webcomic platform with a panel-by-panel reader: pages open full-size, then each tap or arrow key zooms smoothly onto the next panel while the rest of the page dims out. Panels can be any polygon, not just rectangles.
Built to publish classic public-domain novels adapted into comics, but it works for any comic.
Source code: https://github.com/zedred79/broken-panel (MIT, except the "Broken Panel" name and logos)
sitemap.xml and robots.txtStack: Next.js 16, React 19, Prisma 7 + SQLite, Auth.js. Runs as a non-root user (uid 1001).
mkdir -p data/db data/uploads
sudo chown -R 1001:1001 data # the container runs as uid 1001
docker-compose.yml:
services:
broken-panel:
image: zedred/broken-panel:latest
restart: unless-stopped
ports:
- "3000:3000"
environment:
DATABASE_URL: "file:/data/app.db"
UPLOADS_DIR: "/app/uploads"
NEXTAUTH_SECRET: "${NEXTAUTH_SECRET}" # openssl ra
NEXTAUTH_URL: "https://comics.example.com"
ADMIN_EMAIL: "[email protected]"
ADMIN_PASSWORD: "${ADMIN_PASSWORD}" # min. 8 characters
volumes:
- ./data/db:/data
- ./data/uploads:/app/uploads
docker compose up -d
Then open /login and sign in with the admin credentials.
On every start the container syncs the database schema and creates the admin account only if it doesn't exist yet: a password changed from the admin UI is never overwritten on restart or upgrade.
| Variable | Required | Description |
|---|---|---|
NEXTAUTH_SECRET | yes | Secret used to sign sessions |
NEXTAUTH_URL | yes | Public URL of the site |
ADMIN_EMAIL / ADMIN_PASSWORD | first start | Credent |
DATABASE_URL | yes | Keep file:/data/app.db |
UPLOADS_DIR | yes | Keep /app/uploads |
SITE_URL | no | Public URL used for SEO/social metadata, if different from NEXTAUTH_URL |
SITE_HERO_TITLE, SITE_HERO_SUBTITLE, `SITE_FOOTER_TEr texts |
| Path in container | Contents |
|---|---|
/data | SQLite database |
/app/uploads | Uploaded pages, covers, logos and thumbnails |
Back them up together (a plain tar of the data folder is enough).
The app listens on port 3000. Put it behind your reverse proxy (nginx, SWAG, Traefik, Caddy…) with HTTPS, and allow request bodies of at least 25 MB for page uploads. A sample SWAG config is in the rep
To reset a forgotten admin password:
docker exec <container> npx tsx prisma/reset-admin-password.ts
Content type
Image
Digest
sha256:79f55a589…
Size
710 MB
Last updated
about 9 hours ago
docker pull zedred/broken-panel