When the Unlayer editor calls PUT /campaigns/templates/:name to save a
design, the hub writes:
• templates/<name>.html (compiled email-safe HTML)
• templates/<name>.json (Unlayer design tree for editor restore)
• templates/<name>.bak-<ts>.html (backup of previous version)
All three need write access to /app/templates inside the container.
The mount was previously declared as :ro, which made these writes
fail with EROFS (read-only filesystem) once the editor was wired up.
Two changes:
1. Local docker-compose.yml: add ./templates:/app/templates (without
:ro) and ./uploads:/app/uploads (which was already RW on prod but
missing from the committed file — local was out of sync).
2. Prod docker-compose.yml: hot-patched via sed on prod to drop the
:ro flag, then `docker compose down + up -d` to apply the mount
change. PUT verified working (returns 200 with size + design_size).
The /app/lib, /app/server.js, /app/public, /app/package.json mounts stay
:ro since the hub never writes to those — keeping the read-only flag
there is defense-in-depth against compromised code overwriting itself.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
50 lines
1.6 KiB
YAML
50 lines
1.6 KiB
YAML
services:
|
|
targo-hub:
|
|
image: node:20-alpine
|
|
container_name: targo-hub
|
|
working_dir: /app
|
|
volumes:
|
|
- ./server.js:/app/server.js:ro
|
|
- ./lib:/app/lib:ro
|
|
- ./public:/app/public:ro
|
|
- ./package.json:/app/package.json:ro
|
|
- ./data:/app/data
|
|
# Templates RW so the campaign editor can save .html + .json + .mjml
|
|
# files via PUT /campaigns/templates/:name. Was :ro previously which
|
|
# broke save with EROFS — fixed when Unlayer started writing back.
|
|
- ./templates:/app/templates
|
|
# User-uploaded assets (images dragged into the editor)
|
|
- ./uploads:/app/uploads
|
|
- hub_modules:/app/node_modules
|
|
command: sh -c "npm install --production 2>&1 | tail -1 && node server.js"
|
|
env_file: .env
|
|
restart: unless-stopped
|
|
networks:
|
|
- proxy
|
|
- erpnext_erpnext
|
|
- oktopus_oktopus
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.docker.network=proxy"
|
|
|
|
# Main router — webhooks + send + health (no auth)
|
|
- "traefik.http.routers.targo-hub.rule=Host(`msg.gigafibre.ca`)"
|
|
- "traefik.http.routers.targo-hub.entrypoints=websecure"
|
|
- "traefik.http.routers.targo-hub.tls.certresolver=letsencrypt"
|
|
- "traefik.http.services.targo-hub.loadbalancer.server.port=3300"
|
|
|
|
# Disable response buffering for SSE
|
|
- "traefik.http.middlewares.sse-headers.headers.customresponseheaders.X-Accel-Buffering=no"
|
|
- "traefik.http.routers.targo-hub.middlewares=sse-headers"
|
|
|
|
volumes:
|
|
hub_modules:
|
|
|
|
networks:
|
|
proxy:
|
|
external: true
|
|
erpnext_erpnext:
|
|
external: true
|
|
oktopus_oktopus:
|
|
external: true
|