# judge0-py — PRODUCTION stack. # # docker compose -f docker-compose.production.yml up -d --build # # scale workers (see below): # docker compose -f docker-compose.production.yml up -d --scale worker=4 # # Network topology: # public — only the API listens here (port 8000 is the ONLY exposed port) # internal — redis + workers, unreachable from the host # # All services read ../.env. Baseline acceptable: .env with mode 0600 # (chmod 600 .env). In real deployments use Docker secrets or a secrets # manager instead of env vars for REDIS_PASSWORD / API_TOKEN. # # SECURITY NOTES (why these flags): # - cap_add (SYS_ADMIN, SYS_PTRACE, SYS_CHROOT, SYS_RESOURCE, SETUID, # SETGID, MKNOD): nsjail needs to create namespaces/mounts/cgroups/chroots, # trace sandboxed processes, apply cgroup resource limits, switch to an # unprivileged uid inside the sandbox, and mknod /dev/zero. This MINIMAL # cap set was verified to work — `privileged: true` is deliberately NOT # used anywhere. # - seccomp:unconfined at the CONTAINER level: the Docker default seccomp # profile blocks mount/chroot/pivot_root, which nsjail requires. The # sandbox's OWN seccomp policy (worker/sandbox/seccomp.policy) still # applies to untrusted code via nsjail -P — the sandboxed process is # constrained, only the container itself is not. # - cgroup: host (cgroup namespace = host) + entrypoint remount of # /sys/fs/cgroup rw: cgroup v2 delegation, so per-job cgroups can be # created under /sys/fs/cgroup/executor for memory/CPU/pids enforcement. # - read_only rootfs + tmpfs: the worker writes ONLY to /sandbox (per-job # run dirs) and /tmp; the rootfs (/opt/rootfs) and configs (/opt/sandbox) # come from the image and are never writable. # - /var/run/docker.sock is NOT mounted anywhere: workers cannot touch the # host Docker daemon (no docker-in-docker, no daemon compromise path). # - no-new-privileges on the API only: the worker must be able to setuid # inside the sandbox, so it intentionally does not set this. x-logging: &default-logging driver: json-file options: max-size: "10m" max-file: "3" x-worker-recipe: &worker-recipe cap_add: - SYS_ADMIN - SYS_PTRACE - SYS_CHROOT - SYS_RESOURCE - SETUID - SETGID - MKNOD security_opt: - seccomp:unconfined # Docker's CgroupnsMode — `host` so the container sees host cgroups # (field was `cgroupns` in older compose; `cgroup` in compose >= 2.4x). cgroup: host tmpfs: - /tmp:size=128m - /sandbox:size=256m read_only: true services: redis: image: redis:7.2-alpine # $$ -> $ so the password expands at runtime from the container env. command: sh -c 'exec redis-server --requirepass "$$REDIS_PASSWORD" --appendonly yes --appendfsync everysec --maxmemory 512mb --maxmemory-policy noeviction' env_file: .env volumes: - redis-data:/data networks: - internal restart: unless-stopped healthcheck: test: ["CMD-SHELL", "redis-cli -a \"$$REDIS_PASSWORD\" ping | grep -q PONG"] interval: 10s timeout: 3s retries: 5 start_period: 5s mem_limit: 1g logging: *default-logging api: build: context: . dockerfile: docker/Dockerfile.api env_file: .env environment: REDIS_HOST: redis networks: - public - internal ports: - "8081:8000" # the ONLY port exposed to the host restart: unless-stopped healthcheck: test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=3)"] interval: 30s timeout: 5s retries: 3 start_period: 15s read_only: true tmpfs: - /tmp:size=64m security_opt: - no-new-privileges:true user: "10001:10001" # non-root (matches the image's `api` user) depends_on: redis: condition: service_healthy mem_limit: 512m cpus: 2.0 logging: *default-logging worker: build: context: . dockerfile: docker/Dockerfile.worker <<: *worker-recipe env_file: .env environment: REDIS_HOST: redis SANDBOX_ROOT: /sandbox networks: - internal restart: unless-stopped healthcheck: test: ["CMD", "python", "-c", "import redis, os; r = redis.Redis(host=os.environ['REDIS_HOST'], port=int(os.environ.get('REDIS_PORT', '6379')), password=os.environ.get('REDIS_PASSWORD', '')); r.ping()"] interval: 30s timeout: 5s retries: 3 start_period: 10s depends_on: redis: condition: service_healthy mem_limit: 2g cpus: 4.0 pids_limit: 512 # Scale workers with: # docker compose -f docker-compose.production.yml up -d --scale worker=N # WARNING: workers run untrusted code — run them ONLY on a dedicated # execution VM (see README), never on a host with unrelated services. deploy: replicas: 1 logging: *default-logging networks: public: driver: bridge internal: driver: bridge volumes: redis-data: