0bba754f7a
---ci--- project: inboxos phase: 1 milestone: v1.4 status: execute wave: 2 task: T9 ---/ci---
226 lines
7.1 KiB
YAML
226 lines
7.1 KiB
YAML
version: "3.8"
|
|
|
|
# InboxOS — Self-hosted infrastructure stack
|
|
# All services run behind .cloudinit.dev (or your domain)
|
|
# Start: docker compose up -d
|
|
# Stop: docker compose down
|
|
# Data: ./data/<service>/ volumes
|
|
#
|
|
# Board decision: n8n removed — workflow orchestration now runs in
|
|
# Gitea Actions (self-hosted `act_runner`). See INB-8 for the
|
|
# workflow definitions under .gitea/workflows/.
|
|
|
|
networks:
|
|
inboxos-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
gitea-data:
|
|
gitea-config:
|
|
gitea-runner-data:
|
|
gitea-runner-2-data:
|
|
nocodb-data:
|
|
seaweedfs-data:
|
|
papra-data:
|
|
ollama-data:
|
|
|
|
services:
|
|
|
|
# ── Reverse Proxy ──────────────────────────────────────
|
|
# Terminate TLS, route subdomains to services
|
|
# caddy:
|
|
# image: caddy:alpine
|
|
# ports:
|
|
# - "80:80"
|
|
# - "443:443"
|
|
# volumes:
|
|
# - ./caddy/Caddyfile:/etc/caddy/Caddyfile
|
|
# - caddy-data:/data
|
|
# networks:
|
|
# - inboxos-net
|
|
|
|
# ── Ollama — Local LLM Inference ────────────────────────
|
|
ollama:
|
|
image: ollama/ollama:latest
|
|
ports:
|
|
- "11434:11434"
|
|
volumes:
|
|
- ollama-data:/root/.ollama
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
# v1.4 REQ-INB-55: container hardening — read-only rootfs, cap-drop, no-new-privileges
|
|
read_only: false # Ollama needs /root/.ollama to be writable; tmpfs for /tmp
|
|
tmpfs:
|
|
- /tmp:size=64m
|
|
cap_drop: [ALL]
|
|
cap_add: [NET_BIND_SERVICE]
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
# CPU mode by default; for GPU add:
|
|
# deploy:
|
|
# resources:
|
|
# reservations:
|
|
# devices:
|
|
# - driver: nvidia
|
|
# count: all
|
|
# capabilities: [gpu]
|
|
|
|
# ── NocoDB — Database / CRM / ERP Backend ──────────────
|
|
nocodb:
|
|
image: nocodb/nocodb:latest
|
|
ports:
|
|
- "8080:8080"
|
|
volumes:
|
|
- nocodb-data:/usr/app/data
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
# v1.4 REQ-INB-55: container hardening
|
|
read_only: false # NocoDB needs /usr/app/data to be writable
|
|
tmpfs:
|
|
- /tmp:size=128m
|
|
- /run:size=16m
|
|
cap_drop: [ALL]
|
|
cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, NET_BIND_SERVICE]
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
environment:
|
|
NC_AUTH_JWT_SECRET: "${NOCODB_JWT_SECRET:?required}"
|
|
NC_DB: "${NOCODB_DB_URL:-}"
|
|
# Optional: use an external Postgres instead of SQLite
|
|
# NC_DB: "pg://host:port?u=user&p=pass&d=nocodb"
|
|
|
|
# ── Gitea — Version-Controlled Prompts, Scripts, CI/CD ──
|
|
gitea:
|
|
image: gitea/gitea:latest
|
|
ports:
|
|
- "3000:3000"
|
|
- "22:22"
|
|
volumes:
|
|
- gitea-data:/data
|
|
- gitea-config:/etc/gitea
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
# v1.4 REQ-INB-55: container hardening
|
|
read_only: false # Gitea needs /data + /etc/gitea to be writable
|
|
tmpfs:
|
|
- /tmp:size=128m
|
|
- /run:size=16m
|
|
cap_drop: [ALL]
|
|
cap_add: [CHOWN, DAC_OVERRIDE, FOWNER, SETUID, SETGID, NET_BIND_SERVICE, SYS_CHROOT]
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
environment:
|
|
USER_UID: 1000
|
|
USER_GID: 1000
|
|
GITEA__server__DOMAIN: "gitea.cloudinit.dev"
|
|
GITEA__server__HTTP_PORT: 3000
|
|
GITEA__server__ROOT_URL: "https://gitea.cloudinit.dev"
|
|
GITEA__server__SSH_DOMAIN: "gitea.cloudinit.dev"
|
|
# Enable Actions globally; runner is registered as `gitea-runner`
|
|
GITEA__actions__ENABLED: "true"
|
|
|
|
# ── Gitea Actions Runner — Self-hosted CI/CD executor ───
|
|
# Replaces n8n for ALL workflow orchestration.
|
|
# Registers with the Gitea instance using GITEA_RUNNER_REGISTRATION_TOKEN.
|
|
# Workflows are defined as `.gitea/workflows/*.yml` in Gitea repos.
|
|
gitea-runner:
|
|
image: gitea/act_runner:latest
|
|
container_name: gitea-runner
|
|
depends_on:
|
|
- gitea
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
environment:
|
|
GITEA_INSTANCE_URL: "${GITEA_INSTANCE_URL:-http://gitea:3000}"
|
|
GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN:?required}"
|
|
GITEA_RUNNER_NAME: "inboxos-runner-1"
|
|
GITEA_RUNNER_LABELS: "self-hosted,linux,x64,gitea-runner:docker"
|
|
# Cache runner state in a volume so re-registration is not needed
|
|
GITEA_RUNNER_EPHEMERAL: "false"
|
|
volumes:
|
|
- gitea-runner-data:/data
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
# The runner itself runs as a binary, no port exposure needed.
|
|
# It pulls jobs from the Gitea API and executes workflow steps
|
|
# inside ephemeral Docker containers on the host.
|
|
|
|
# ── Gitea Actions Runner #2 — Parallel executor (INB-33) ─
|
|
# Same image + labels as gitea-runner so workflows targeting
|
|
# `ubuntu-latest` / `self-hosted` can dispatch to either runner.
|
|
# Removes the single-runner contention between the 5-min
|
|
# live-pipeline cron and the long-running historical pipeline.
|
|
# Requires its own one-time registration token; long-lived
|
|
# token is persisted in the gitea-runner-2-data named volume.
|
|
gitea-runner-2:
|
|
image: gitea/act_runner:latest
|
|
container_name: gitea-runner-2
|
|
depends_on:
|
|
- gitea
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
environment:
|
|
GITEA_INSTANCE_URL: "${GITEA_INSTANCE_URL:-http://gitea:3000}"
|
|
GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN_2:?required}"
|
|
GITEA_RUNNER_NAME: "inboxos-runner-2"
|
|
GITEA_RUNNER_LABELS: "self-hosted,linux,x64,gitea-runner:docker"
|
|
GITEA_RUNNER_EPHEMERAL: "false"
|
|
volumes:
|
|
- gitea-runner-2-data:/data
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
|
|
# ── SeaweedFS — Distributed File Storage ───────────────
|
|
seaweedfs-master:
|
|
image: chrislusf/seaweedfs:latest
|
|
ports:
|
|
- "9333:9333"
|
|
command: "master -ip=seaweedfs-master"
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
|
|
seaweedfs-volume:
|
|
image: chrislusf/seaweedfs:latest
|
|
ports:
|
|
- "8081:8080"
|
|
command: 'volume -ip=seaweedfs-volume -max=100 -mserver="seaweedfs-master:9333"'
|
|
volumes:
|
|
- seaweedfs-data:/data
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- seaweedfs-master
|
|
|
|
seaweedfs-filer:
|
|
image: chrislusf/seaweedfs:latest
|
|
ports:
|
|
- "8888:8888"
|
|
command: 'filer -ip=seaweedfs-filer -master="seaweedfs-master:9333"'
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- seaweedfs-master
|
|
|
|
# ── Papra — Email / Document Ingestion ─────────────────
|
|
papra:
|
|
image: ghcr.io/papra-hq/papra:latest
|
|
ports:
|
|
- "1221:1221"
|
|
volumes:
|
|
- papra-data:/data
|
|
networks:
|
|
- inboxos-net
|
|
restart: unless-stopped
|
|
environment:
|
|
AUTH_SECRET: "${PAPRA_AUTH_SECRET:?required}"
|
|
# INB-27 catch-all intake was cancelled on 2026-06-08 — see
|
|
# docs/papra-write-policy.md §Why a policy, not a blanket copy.
|
|
# Papra writes are pipeline-driven only (PAPRA_DRY_RUN=true by
|
|
# default; live writes gated behind Phase 6).
|