Files
mangaBookmark/docker-compose.yml
T
sulthan 3adbfb7ad9 fix: scope headless-shell to a dedicated network, off the Traefik proxy network
Prod override put headless-shell on the externally-managed `proxy`
network so manga-api (confined there for Traefik routing) could still
resolve it. That reopened CDP (port 9222, raw remote code execution)
to every other container on that shared network, not just manga-api.

Give both services a project-private `browser` network (defined in
the base compose file, not `internal: true` since headless Chrome
needs outbound access to kagane.to). manga-api joins both `proxy` and
`browser` in the prod override; headless-shell never touches `proxy`.
2026-08-03 17:52:34 +07:00

77 lines
3.3 KiB
YAML

# Base stack — works standalone for local smoke testing (`docker compose up`).
# The service binds 127.0.0.1:8080; a host reverse proxy (nginx/Caddy/Traefik)
# terminates TLS for manga-api.<domain> and forwards to it.
#
# If your proxy runs in Docker on its own network, use the prod override which
# attaches to that network instead of publishing a port:
# docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
services:
manga-api:
build: ./backend
image: mangabm-backend:latest
container_name: manga-api
restart: unless-stopped
environment:
# API_TOKEN is required — compose refuses to start without it.
API_TOKEN: ${API_TOKEN:?set API_TOKEN in .env}
ALLOWED_ORIGINS: ${ALLOWED_ORIGINS:-https://asuracomic.net,https://asurascans.com,https://demonicscans.org}
DB_PATH: /data/bookmarks.db
PORT: "8080"
# Gates the browser UI. Unset means the web routes are not served at all.
WEB_PASSWORD: ${WEB_PASSWORD:-}
# Path inside the container; matches the bindmount above.
USERSCRIPT_PATH: ${USERSCRIPT_PATH:-/userscript/manga-bookmark.user.js}
# Latest-chapter poller. LATEST_CHAPTER_POLL_ENABLED=0 in .env is the kill
# switch; it only takes effect because these are listed here.
LATEST_CHAPTER_POLL_ENABLED: ${LATEST_CHAPTER_POLL_ENABLED:-1}
LATEST_CHAPTER_POLL_COOLDOWN: ${LATEST_CHAPTER_POLL_COOLDOWN:-1h}
LATEST_CHAPTER_POLL_INTERVAL: ${LATEST_CHAPTER_POLL_INTERVAL:-10m}
LATEST_CHAPTER_POLL_BATCH: ${LATEST_CHAPTER_POLL_BATCH:-14}
LATEST_CHAPTER_POLL_STAGGER: ${LATEST_CHAPTER_POLL_STAGGER:-20s}
# CDP endpoint for sites behind a JavaScript challenge (kagane). Unset
# disables browser polling for those sites; the userscript still covers them.
BROWSER_WS_URL: ${BROWSER_WS_URL:-ws://headless-shell:9222}
depends_on:
- headless-shell
volumes:
- bookmarks-data:/data
# The userscript is served from here, read fresh on every request. Editing
# the file in this checkout takes effect on the next Violentmonkey poll —
# no rebuild, no restart. `git pull` restores the committed version, which
# is why a redeploy always ships the repo's script.
- ./userscript:/userscript:ro
# Bound to loopback only: the proxy (or curl during smoke test) reaches it,
# the public internet does not.
ports:
- "127.0.0.1:8080:8080"
networks:
- browser
headless-shell:
image: chromedp/headless-shell:stable
restart: unless-stopped
# Chrome allocates shared memory per tab and dies on Docker's 64MB default.
shm_size: '1gb'
# Reaps zombie renderer processes, which otherwise accumulate for the
# container's lifetime.
init: true
# Deliberately no `ports:` — an exposed CDP endpoint is remote code
# execution. Only manga-api, via the `browser` network below, may reach it.
command:
- --remote-debugging-address=0.0.0.0
- --remote-debugging-port=9222
- --disable-gpu
- --no-sandbox
networks:
- browser
volumes:
bookmarks-data:
networks:
# Not `internal: true`: headless Chrome still needs outbound access to reach
# kagane.to. Isolation here comes from membership (only manga-api and
# headless-shell join it), not from cutting egress.
browser: