178 lines
5.5 KiB
YAML
178 lines
5.5 KiB
YAML
name: codexpi
|
|
|
|
x-app: &app
|
|
image: ${CODEXPI_IMAGE:-codexpi-app:local}
|
|
build:
|
|
context: .
|
|
target: production
|
|
args:
|
|
CODEX_VERSION: ${CODEX_VERSION:-latest}
|
|
CLAUDE_CODE_VERSION: ${CLAUDE_CODE_VERSION:-latest}
|
|
OPENCODE_VERSION: ${OPENCODE_VERSION:-latest}
|
|
env_file:
|
|
- .env
|
|
restart: unless-stopped
|
|
init: true
|
|
user: "${PUID:-1000}:${PGID:-1000}"
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
cap_drop:
|
|
- ALL
|
|
read_only: true
|
|
tmpfs:
|
|
- /tmp:size=128m,mode=1777
|
|
volumes:
|
|
- ./data:/app/data
|
|
depends_on:
|
|
data-permissions:
|
|
condition: service_completed_successfully
|
|
|
|
services:
|
|
data-permissions:
|
|
image: codexpi-app:local
|
|
user: "0:0"
|
|
restart: "no"
|
|
read_only: true
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
cap_drop:
|
|
- ALL
|
|
cap_add:
|
|
- CHOWN
|
|
- DAC_OVERRIDE
|
|
- FOWNER
|
|
volumes:
|
|
- ./data:/app/data
|
|
entrypoint: ["sh", "-c"]
|
|
command: ["chown -R \"$${PUID}:$${PGID}\" /app/data && chmod 700 /app/data"]
|
|
environment:
|
|
PUID: "${PUID:-1000}"
|
|
PGID: "${PGID:-1000}"
|
|
|
|
django-web:
|
|
<<: *app
|
|
command: >-
|
|
sh -c "python3 manage.py migrate --noinput &&
|
|
python3 -m uvicorn django_ui.asgi:application --host 0.0.0.0 --port 8001 --proxy-headers --forwarded-allow-ips='*'"
|
|
environment:
|
|
CODEXPI_DATA_DIR: /app/data
|
|
CODEXPI_COOKIE_SECURE: "${CODEXPI_COOKIE_SECURE:-false}"
|
|
healthcheck:
|
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8001/api/bootstrap', timeout=3)"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 20s
|
|
|
|
api:
|
|
<<: *app
|
|
# Codex workspace-write uses Bubblewrap, which creates an unprivileged user
|
|
# namespace. Docker's default seccomp profile blocks that syscall before
|
|
# Bubblewrap can apply its own (stricter) filesystem sandbox.
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
- seccomp:unconfined
|
|
command: >-
|
|
python3 -m uvicorn codexpi.main:app --app-dir /app/backend
|
|
--host 0.0.0.0 --port 8000 --proxy-headers --forwarded-allow-ips='*'
|
|
environment:
|
|
CODEXPI_BASE_DIR: /app
|
|
CODEXPI_DATA_DIR: /app/data
|
|
CODEXPI_DATABASE: /app/data/codexpi.db
|
|
CODEXPI_HOST: 0.0.0.0
|
|
CODEXPI_PORT: 8000
|
|
CODEXPI_AUTH_BACKEND: django
|
|
CODEXPI_CONTAINERIZED: "true"
|
|
CODEXPI_COOKIE_SECURE: "${CODEXPI_COOKIE_SECURE:-false}"
|
|
CODEXPI_BROWSER_WEBDRIVER_URL: http://browser:4444
|
|
CODEXPI_BROWSER_CONTAINER: codexpi-browser-1
|
|
CODEXPI_BROWSER_SCREENSHOTS: /app/data/browser/screenshots
|
|
CODEXPI_APP_STORE_AUTH_URL: "${CODEXPI_APP_STORE_AUTH_URL:-http://192.168.1.63:16002/auth/me}"
|
|
HOME: /home/codex
|
|
group_add:
|
|
- "${DOCKER_GID:-998}"
|
|
volumes:
|
|
- ./data:/app/data
|
|
- type: bind
|
|
source: /home/config
|
|
target: /home/config
|
|
bind:
|
|
propagation: rslave
|
|
- ${CODEXPI_HOME:-/home/config}/.codex:/home/codex/.codex
|
|
- ${CODEXPI_HOME:-/home/config}/.claude:/home/codex/.claude
|
|
- ${CODEXPI_HOME:-/home/config}/.config/opencode:/home/codex/.config/opencode
|
|
- ${CODEXPI_HOME:-/home/config}/.local/share/opencode:/home/codex/.local/share/opencode
|
|
- ${CODEXPI_HOME:-/home/config}/.config/gh:/home/codex/.config/gh
|
|
- /var/run/docker.sock:/var/run/docker.sock
|
|
healthcheck:
|
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/api/health', timeout=3)"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 15s
|
|
|
|
browser:
|
|
image: ${CODEXPI_BROWSER_IMAGE:-selenium/standalone-chromium:latest}
|
|
command: ["/opt/bin/codexpi-browser-entrypoint"]
|
|
restart: unless-stopped
|
|
shm_size: "1gb"
|
|
environment:
|
|
SE_NODE_MAX_SESSIONS: "1"
|
|
SE_VNC_NO_PASSWORD: "true"
|
|
SE_SCREEN_WIDTH: "1440"
|
|
SE_SCREEN_HEIGHT: "900"
|
|
SE_SCREEN_DEPTH: "24"
|
|
volumes:
|
|
- browser-home:/home/seluser
|
|
- ./scripts/browser-entrypoint.sh:/opt/bin/codexpi-browser-entrypoint:ro
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
|
|
nginx:
|
|
image: nginx:1.27-alpine
|
|
restart: unless-stopped
|
|
user: "101:101"
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
django_upstream="$${CODEXPI_DJANGO_UPSTREAM:-django-web}"
|
|
api_upstream="$${CODEXPI_API_UPSTREAM:-api}"
|
|
echo "Attente de Django ($${django_upstream}) et FastAPI ($${api_upstream})…"
|
|
until wget -qO- "http://$${django_upstream}:8001/api/bootstrap" >/dev/null 2>&1 \
|
|
&& wget -qO- "http://$${api_upstream}:8000/api/health" >/dev/null 2>&1; do
|
|
sleep 2
|
|
done
|
|
echo "upstream django_ui { server $${django_upstream}:8001; }" > /tmp/codexpi-upstreams.conf
|
|
echo "upstream fastapi_api { server $${api_upstream}:8000; }" >> /tmp/codexpi-upstreams.conf
|
|
cat /tmp/codexpi-upstreams.conf
|
|
exec nginx -g 'daemon off;'
|
|
read_only: true
|
|
security_opt:
|
|
- no-new-privileges:true
|
|
cap_drop:
|
|
- ALL
|
|
tmpfs:
|
|
- /tmp:size=16m,mode=1777
|
|
- /var/cache/nginx:size=16m,mode=0755
|
|
volumes:
|
|
- ./deploy/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
|
ports:
|
|
- "${CODEXPI_BIND_ADDRESS:-0.0.0.0}:${CODEXPI_PROXY_PORT:-8765}:8080"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "-qO-", "http://127.0.0.1:8080/proxy-health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 5
|
|
start_period: 30s
|
|
depends_on:
|
|
django-web:
|
|
condition: service_healthy
|
|
api:
|
|
condition: service_healthy
|
|
browser:
|
|
condition: service_started
|
|
|
|
volumes:
|
|
browser-home:
|