# The hosted $FLY vault fly (docs/vault/SPEC.md). Installed by bootstrap-host.sh as /srv/fly/docker-compose.yml and # driven by the updater (fly_update.py): FLY_RELEASE picks the image tags it built from a verified release. # Nothing listens on a public interface: the console is on 127.0.0.1 (reach it with an SSH tunnel over Tailscale) and # the containers only make outbound connections. # # fly-signer the ONLY container that can read the two hot keys; it checks and signs, over a Unix socket in # /srv/fly/run (a directory only it and the brain's group can enter). Read-only filesystem, no capabilities. # fly the brain: engine, console, vault worker, data feeds. No key is mounted into it. # db Postgres on an internal network (no route out), its password generated by bootstrap-host.sh. # # Settings: /srv/fly/custody.env (public: the treasury multisig, its limits, the float and caps; read by both), # /srv/fly/signer.env (the signer's own RPC URL), /srv/fly/vault.env (the brain's API keys, relay, Telegram, backups), # /srv/fly/db.env (the database password). All root-only (0600) on the host. services: db: image: postgres:17@sha256:d74eeac9a635390a49bc21bd49fccd973de707e2a53a76ac49b552b8712ec46f env_file: /srv/fly/db.env environment: POSTGRES_USER: fly POSTGRES_DB: fly_trader volumes: - pgdata:/var/lib/postgresql/data networks: [internal] healthcheck: test: ["CMD-SHELL", "pg_isready -U fly -d fly_trader"] interval: 5s timeout: 3s retries: 30 restart: unless-stopped security_opt: ["no-new-privileges:true"] logging: {driver: json-file, options: {max-size: "20m", max-file: "5"}} fly-signer: image: fly-signer:${FLY_RELEASE:-none} env_file: [/srv/fly/custody.env, /srv/fly/signer.env] environment: BOT_PRIVATE_KEY_FILE: /run/secrets/bot_key PAYOUT_PRIVATE_KEY_FILE: /run/secrets/payout_key SIGNER_SOCKET: /run/fly-signer/sock SIGNER_DB: /srv/signer/ledger.sqlite volumes: - /srv/fly/secrets/bot_key:/run/secrets/bot_key:ro - /srv/fly/secrets/payout_key:/run/secrets/payout_key:ro - /srv/fly/signer:/srv/signer - /srv/fly/run:/run/fly-signer networks: [egress] # its own RPC and Jupiter's price API; nothing reaches in read_only: true tmpfs: [/tmp] restart: unless-stopped security_opt: ["no-new-privileges:true"] cap_drop: [ALL] mem_limit: 512m pids_limit: 64 logging: {driver: json-file, options: {max-size: "20m", max-file: "5"}} fly: image: fly-trader:${FLY_RELEASE:-none} env_file: [/srv/fly/custody.env, /srv/fly/vault.env, /srv/fly/db.env] environment: DATA_DIR: /app/data LOG_DIR: /app/logs SIGNER_SOCKET: /run/fly-signer/sock VAULT_ENABLED: "1" AUTO_UPDATE: "0" # releases come through the host updater (signed, 24 h hold on code), never from inside volumes: - /srv/fly/data:/app/data - /srv/fly/logs:/app/logs - /srv/fly/run:/run/fly-signer - ${FLY_RELEASES:-/srv/fly/releases}:/releases:ro networks: [internal, egress] ports: - "127.0.0.1:8501:8501" depends_on: db: condition: service_healthy fly-signer: condition: service_healthy restart: unless-stopped stop_grace_period: 150s security_opt: ["no-new-privileges:true"] cap_drop: [ALL] mem_limit: 48g # a dedicated 128 GB box; lower it on a smaller one (16 GB: 14g) logging: {driver: json-file, options: {max-size: "50m", max-file: "5"}} networks: internal: internal: true # the database has no route to anything but its two clients egress: {} volumes: pgdata: