fly-trader / Dockerfile
bryceweiner's picture
release 5 (acbf4d30d3)
2b7ab0e verified
Raw History Blame Contribute Delete
3.71 kB
# fly-trader: the trading engine and its console, on the CPU (a Linux container has no Metal GPU; the fly infers fine on CPU).
# Two images from one file:
# default (last stage, "brain"): the engine, console and vault worker. It holds NO wallet key.
# --target signer: the only holder of the hot keys (fly_trader/signer): standard library + solders + httpx, nothing
# else of fly_trader, so none of the brain's dependencies ever run next to a key.
# Base images are pinned by digest and every Python dependency by hash (uv.lock, docker/signer-requirements.txt): a
# release builds exactly what was reviewed, whatever the registries serve later.
ARG PYTHON_IMAGE=python:3.13-slim@sha256:7c61056e61ac89e852de05f3dc6fa51a6dd2181797bceed46aa725dd7cb2cd3b
# ---------------------------------------------------------------- the signer
FROM ${PYTHON_IMAGE} AS signer
ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/app
RUN apt-get update && apt-get install -y --no-install-recommends age && rm -rf /var/lib/apt/lists/* \
&& groupadd -g 10000 fly && useradd -u 10002 -g 10000 -M -s /usr/sbin/nologin signer
COPY docker/signer-requirements.txt /tmp/req.txt
RUN pip install --no-cache-dir --require-hashes --only-binary=:all: -r /tmp/req.txt && rm /tmp/req.txt
WORKDIR /app
COPY fly_trader/__init__.py fly_trader/__init__.py
COPY fly_trader/signer fly_trader/signer
USER 10002:10000
HEALTHCHECK --interval=30s --timeout=10s --start-period=20s --retries=3 CMD python -m fly_trader.signer ping || exit 1
CMD ["python", "-m", "fly_trader.signer", "serve"]
# ---------------------------------------------------------------- the brain (default)
FROM ${PYTHON_IMAGE} AS brain
ENV PYTHONUNBUFFERED=1 \
UV_PROJECT_ENVIRONMENT=/app/.venv \
UV_COMPILE_BYTECODE=1 \
HOME=/tmp \
PATH="/app/.venv/bin:${PATH}"
# openssh-client: ssh-keygen verifies signed releases (the self-updater, fly_trader/ops/autoupdate.py)
RUN apt-get update && apt-get install -y --no-install-recommends postgresql-client ca-certificates curl age openssh-client \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd -g 10000 fly && useradd -u 10001 -g 10000 -M -s /usr/sbin/nologin fly
COPY --from=ghcr.io/astral-sh/uv:0.12.19@sha256:04d046b13e60d6bcec73cbc5e1cad25d680dea90c8573340950a0ac2d1aef424 /uv /usr/local/bin/uv
WORKDIR /app
COPY . .
# Which torch: TORCH_INDEX=cpu (default: any machine, ~200 MB), cu126 / cu130 for an NVIDIA GPU (the CUDA runtime is
# inside the wheel; the host needs only the driver and the NVIDIA Container Toolkit -- see docker-compose.cuda.yml), or
# rocm72 for an AMD GPU (the ROCm runtime is inside the wheel; the host needs only the amdgpu driver --
# docker-compose.rocm.yml).
# It names the project extra that pins torch to that index. --frozen: exactly uv.lock with its hashes, never a fresh
# resolution. The project installs editable: the package IS /app/fly_trader, so config.REPO_ROOT is /app.
ARG TORCH_INDEX=cpu
ENV TORCH_INDEX=${TORCH_INDEX}
RUN uv sync --frozen --no-dev --extra "${TORCH_INDEX}" \
&& chmod +x /app/docker/entrypoint.sh \
&& mkdir -p /app/fly_trader/ui/static/brain && chown -R 10001:10000 /app/fly_trader/ui/static/brain
# The vault server runs this unprivileged user and never updates itself (AUTO_UPDATE=0). The self-hosted compose file
# runs the container as root instead: its self-updater replaces /app, and it writes the bind-mounted data/, logs/, .env.
USER 10001:10000
EXPOSE 8501
# healthy = the database answers, the console's worker threads run and (on the vault server) the signer answers
HEALTHCHECK --interval=30s --timeout=20s --start-period=300s --retries=5 CMD fly-trader health || exit 1
ENTRYPOINT ["/app/docker/entrypoint.sh"]