Sign inSign up
broken-panel logo

zedred/broken-panel

By zedred

•Updated about 9 hours ago

Self-hosted webcomic reader with panel-by-panel zoom. Next.js + SQLite, single-admin CMS.

Image
Content management system
0

486

zedred/broken-panel repository overview

⁠Broken Panel

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)

⁠Features

  • Immersive reader: automatic zoom on each panel, quick-jump grid of page thumbnails, chapters, and reading progress saved in the browser ("Continue from page X")
  • Admin area (single account): create comics, upload pages, draw panel polygons on each page, reorder pages and panels, group pages into chapters
  • Custom branding: header and homepage logos uploaded from the admin panel; homepage and footer texts set through environment variables
  • SEO-ready: per-comic OpenGraph/Twitter cards, dynamic sitemap.xml and robots.txt
  • Hardened uploads: size limits, file content checked SVG sanitization, automatic WebP thumbnails
  • Login protection: rate limiting by IP and by email, constant-time responses

Stack: Next.js 16, React 19, Prisma 7 + SQLite, Auth.js. Runs as a non-root user (uid 1001).

⁠Quick start

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.

⁠Environment variables

VariableRequiredDescription
NEXTAUTH_SECRETyesSecret used to sign sessions
NEXTAUTH_URLyesPublic URL of the site
ADMIN_EMAIL / ADMIN_PASSWORDfirst startCredent
DATABASE_URLyesKeep file:/data/app.db
UPLOADS_DIRyesKeep /app/uploads
SITE_URLnoPublic URL used for SEO/social metadata, if different from NEXTAUTH_URL
SITE_HERO_TITLE, SITE_HERO_SUBTITLE, `SITE_FOOTER_TEr texts

⁠Volumes

Path in containerContents
/dataSQLite database
/app/uploadsUploaded pages, covers, logos and thumbnails

Back them up together (a plain tar of the data folder is enough).

⁠Reverse proxy

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

Tag summary

Content type

Image

Digest

sha256:79f55a589…

Size

710 MB

Last updated

about 9 hours ago

docker pull zedred/broken-panel