FLIP is an open-source platform for federated training and evaluation of medical imaging AI models across healthcare institutions, while ensuring data privacy and security.
FLIP is developed by the London AI Centre in collaboration with Guy's and St Thomas' NHS Foundation Trust and King's College London.
This repository is the FLIP mono-repo: Central Hub API, Trust APIs, UI, Docker deployment, and the federated
learning code (base library, FL services, and tutorials) for both NVFLARE and Flower. The FL code lives under
flip-utils/ (the pip-installable flip package), fl-services/ (Docker services
for FL server/client/API), and fl-apps/ (job-type implementations and tutorials).
| Subdirectory | Description |
|---|---|
flip-api/ |
Central Hub API service |
flip-ui/ |
Frontend UI |
trust/ |
Trust-side services (trust-api, imaging-api, data-access-api, mock OMOP / Orthanc / XNAT) |
deploy/ |
Docker Compose and infrastructure-as-code (AWS / on-prem) |
docs/ |
Sphinx documentation source (ReadTheDocs) |
flip-utils/ |
flip Python package — platform logic, NVFLARE components, Flower helpers |
fl-services/ |
Docker images for FL networks, nested per backend (fl-services/nvflare/: fl-server, fl-client, fl-api-base, fl-base; fl-services/flower/: superlink, supernode, fl-api-flower, fl-base) |
fl-apps/ |
FL job-type implementations / app templates, nested per backend (fl-apps/nvflare/: standard, evaluation, diffusion_model, fed_opt; fl-apps/flower/: standard, evaluation) |
fl-tutorials/ |
End-to-end tutorial examples, nested per backend (fl-tutorials/nvflare/: xray classification, spleen seg/eval, diffusion; fl-tutorials/flower/: xray classification, spleen seg/eval, numpy) |
Both backends are now provisioned in-tree (gitignored): the NVFLARE workspace at
fl-services/nvflare/provision/workspace-dev (make -C fl-services/nvflare provision) and the Flower certs
at fl-services/flower/provision/creds (make -C fl-services/flower provision) — see
Federated Learning Setup below.
- Docker with Swarm mode initialized
- Nvidia Container Toolkit
- Make
- UV - Python environment management tool
- postgresql-client (install with
apt install postgresql-client postgresql-client-commonon Debian/Ubuntu) - jq - JSON processor used by several
maketargets (install withapt install jqon Debian/Ubuntu, orbrew install jq)
For developer tooling and IDE setup, see CONTRIBUTING.md.
To start the services, you can use the Makefile provided in the root directory. The Makefile provides several convenient commands to manage the services defined in the deploy/compose.development.yml file.
For example:
| Command | Description |
|---|---|
make up |
Run all services using Docker Swarm for XNAT (make build first if needed) |
make up-no-trust |
Run all services except the trust services related services |
make up-trusts |
Run the trust services related services (uses Docker Swarm for XNAT) |
make central-hub |
Run the central API service and the database (does not start the UI — use make ui for that) |
make ui |
Start the flip-ui container only (no-op when PROD=stag/PROD=true, since the UI is served from S3 + CloudFront) |
make ui-off |
Stop the flip-ui container |
make build |
Build all Docker images |
make down |
Stop all services and remove the containers (including Swarm stacks) |
make restart |
Stop and start all services |
make restart-no-trust |
Stop and start all services except the trust services related services |
make clean |
Remove all stopped containers, networks, and images |
make ci |
Run the CI pipeline locally using act |
make -C trust up-trust KIT=<CODE> PROD=<env> |
Run a trust on the local host pointing at a remote hub (kit file trust/.env.<CODE>.<env>; the on-prem trust kit is trust/.env.<CODE>.production) |
make new-trust TRUST_CODE=<CODE> TRUST_NAME="..." |
Scaffold a new trust kit file trust/.env.<CODE>.<env> from the base template |
make register-trusts |
Register the shipped dev roster (trust/.env.*.development.example, currently GSTT + KCH) on the running hub and write per-trust kit files (run automatically by make up) |
make register-trust KIT=<CODE> |
Register one trust on the running hub and fill its kit file (creds + hub-shared block) |
make unit_test |
Run unit tests across all services |
make tests |
Run flip-ui unit and e2e tests followed by the full flip-api test suite (lint + mypy + pytest) |
make e2e_smoke |
Drive a full project lifecycle (create → upload → train → download) against an already-running stack (not run in CI) |
make lock |
Regenerate every service's uv.lock from its pyproject.toml |
make debug SERVICE=<name> |
Restart one service in debug mode (waits for a debugger on port 5678). Services: flip-api, fl-api-net-1, trust-api, imaging-api, data-access-api |
make debug-off SERVICE=<name> |
Take a single service back out of debug mode |
make debug-all |
Restart every API service in debug mode |
make debug-off-all |
Take every API service back out of debug mode |
You can add new commands to the Makefile to create smaller deployments for testing and development.
The XNAT services are deployed using Docker Swarm mode for better resource management and scalability. Docker Swarm is automatically used when running make up or make up-trusts.
Key features of Swarm deployment:
- Better resource allocation with CPU and memory limits
- Automatic service recovery with restart policies
- Overlay networking for secure service communication
- Support for multi-node deployment (if configured)
Swarm-specific commands:
- XNAT services are deployed as Docker stacks (
xnat1andxnat2) - The Swarm deployment uses the trust/xnat/docker-compose-stack.yml file
- Networks are created as overlay networks with
--attachableflag for flexibility
Note: Docker Swarm mode must be initialized on your system. If not already initialized, run:
docker swarm initAfter that, you will need to restart the docker networks used by the services:
there is a command to create the networks, but you will need to remove them manually first if they are already running:
docker network rm deploy_trust-network-1
docker network rm deploy_trust-network-2Then create the networks again:
make create-networksTo manually manage XNAT services (uses Docker Swarm):
cd trust/xnat
make up # Start XNAT services
make down # Stop XNAT services
make xnat-shell # Get a shell in the XNAT containerThe internal service key (for fl-server-to-hub authentication) must be generated before starting the platform:
make generate-internal-service-keyThis writes INTERNAL_SERVICE_KEY and INTERNAL_SERVICE_KEY_HASH (plain strings) into .env.development. make up invokes generate-internal-service-key automatically.
Trusts are registered on the running hub rather than configured via env-file key dicts:
make register-trustsregister-trusts registers the shipped dev roster — every trust/.env.*.development.example kit (currently GSTT and KCH) — on the hub: for each, it inserts a trust row with its api_key_hash, claims an FL kit slot, and fills that trust's kit file trust/.env.<CODE>.development (with TRUST_API_KEY, TRUST_INTERNAL_SERVICE_KEY, FL_KIT_SLOT, FL_KIT_SLOT_NUMBER, EXPECTED_TRUST_ID). The kit files ARE the roster — trusts are not enumerated in the hub env file. To add another, run make new-trust TRUST_CODE=<CODE> TRUST_NAME="..." then make register-trust KIT=<CODE>. make up runs register-trusts automatically once the hub is up. See CLAUDE.md for the trust-internal auth threat model.
To start the full platform locally:
make upThis will start all the services defined in the deploy/compose.development.yml file. The services will be started in detached
mode, so you can continue using your terminal. Use docker compose ps to see the status of the services and see which
ports they are running on.
To get a shell some of the services, you can run:
docker compose -f deploy/compose.development.yml exec < service-name > < command >For example:
docker compose -f deploy/compose.development.yml exec flip-ui /bin/shThis will give you a shell in the flip-ui container. You can run any command inside the container, including
installing new packages, running tests, and debugging the code.
To stop the services:
make downIf you want to run a single service you can run:
docker compose -f deploy/compose.development.yml run --rm < service name >FLIP supports NVIDIA FLARE and Flower. Each backend
provisions its own credentials in-tree (gitignored) under fl-services/<backend>/provision/ — NVFLARE per-participant
startup kits, Flower TLS certs + SuperNode keys — and its own Makefile owns the workflow. Provision before make up:
the credentials are not generated by make up, and the FL containers crash-loop without them (NVFLARE's fl-server
logs start.sh: No such file or directory).
make -C fl-services/nvflare provision-2-nets # NVFLARE dev: net-1 + net-2
make -C fl-services/flower provision NET_NUMBER=1 # Flower dev: per-net (repeat for net-2)See each backend's README for the full workflow — dev vs over-provisioned stag/prod, S3 upload, onboarding:
fl-services/nvflare/README.md and
fl-services/flower/README.md. FL_PROVISIONED_DIR (the host dir Docker mounts) is
derived from FL_BACKEND in deploy/fl_backend.mk; override for a one-off with
make up FL_PROVISIONED_DIR=/tmp/ws.
Each backend owns its FL lifecycle in its own fl-services/<backend>/Makefile: build, provision, up, down, and
submit (Flower additionally has up-secure for the TLS + SuperNode-auth stack). The root Makefile only forwards
build-fl to the selected backend (make build-fl FL_BACKEND=<backend>, mirroring the fl-tutorials/ forwarder); the
standalone run / provision / submit targets are invoked directly on the backend Makefile.
To run the FL services standalone and submit a job without the full FLIP stack:
make -C fl-services/<backend> provision NET_NUMBER=1 # flower: per-net certs+keys; nvflare: startup kits (provision-2-nets for both nets)
make -C fl-services/<backend> up # flower also: up-secure (needs provision first)
make -C fl-services/<backend> submit APP=<job>
make -C fl-services/<backend> downThe in-tree tutorials under fl-tutorials/ run on each backend's local simulator. The root
fl-tutorials/ Makefile forwards every target to the selected backend (FL_BACKEND, default nvflare; pass
FL_BACKEND=flower for Flower):
make -C fl-tutorials download-xray-data # one-off: fetch the xray dataset (HF); spleen: download-spleen-data
make -C fl-tutorials list-tutorials
make -C fl-tutorials run-tutorial TUTORIAL=xray_classification # add FL_BACKEND=flower for the Flower tutorial
make -C fl-tutorials run-all-tutorialsNVFLARE tutorials need a GPU + the flare-fl-base image. Build the FL images locally first (tagged :dev) with
make build-fl. See each tutorial's README for backend-specific details.
All commands run from deploy/providers/AWS/. The Makefile defaults to staging when PROD is unset.
Prerequisites:
~/.ssh/host-awsSSH key configured.env.stagand per-service.envfiles present (see deploy/README.md)~/.aws/confighas a[profile stag]alias for the staging accountDOCKER_TAGin.env.stagis a published GHCR tag (branch images don't auto-build — triggergh workflow run docker_build_flip_api.yml --ref <branch>if needed)
One-shot deploy:
cd deploy/providers/AWS
export AWS_PROFILE=stag
make full-deploy PROD=stagThis runs the following steps in order:
| Step | Command | What it does |
|---|---|---|
| 1 | make github-login |
Authenticate with GitHub CLI |
| 2 | make aws-login |
AWS SSO login |
| 3 | make init |
Terraform init with the S3 backend |
| 4 | make import-persistent |
Import pre-existing resources (Cognito, Secrets, S3 buckets) — idempotent, safe to re-run |
| 5 | make generate-internal-service-key |
Mint the fl-server → hub INTERNAL_SERVICE_KEY (idempotent) |
| 6 | make plan |
Review the Terraform execution plan |
| 7 | make apply |
Apply infrastructure changes |
| 8 | make update-env |
Refresh .env.stag with Terraform outputs |
| 9 | make ssh-config |
Write SSM-tunnelled Host flip / Host flip-trust into ~/.ssh/config |
| 10 | make ansible-init |
Configure Trust EC2 with Docker, CloudWatch, and FL assets |
| 11 | make deploy-centralhub |
Deploy ECS Fargate services at the env branch tip (immutable sha-<short7> task-def revisions) + sync UI to S3 + invalidate CloudFront |
| 12 | make register-trusts |
Register trusts on the hub and write per-trust kit files |
| 13 | make deploy-trust |
Deploy trust stack to Trust EC2 via Docker Compose |
| 14 | make status |
Health checks |
Stag note: staging has ~70 resources missing from Terraform state.
make import-persistent(step 4) handles this — it probes state before each import and skips already-imported resources, so re-running after a failure is safe.
For subsequent UI-only changes: make deploy-ui PROD=stag (no Terraform involved).
For production, replace PROD=stag with PROD=true and AWS_PROFILE=prod throughout.
For full details, prerequisites, and troubleshooting see deploy/providers/AWS/README.md.
To connect a local Ubuntu host as a trust against the AWS staging hub:
# One-shot hybrid deploy (auto-detects your public IP)
cd deploy/providers/AWS
make full-deploy-hybrid PROD=stag [LOCAL_TRUST_IP=<public-ip>]
# Then on the trust host: provision it, then start the stack
make provision-local-trust # run ON the trust host
cd ../../..
env PROD=stag make -C trust up-trust KIT=<CODE>No inbound firewall rules or NAT port-forwarding are needed on the trust host — all communication is outbound from the trust to the hub. See deploy/providers/local/README.md for full details.
Early-access. The three blockers from the original k3s validation (
xnat-nginxexiting after entrypoint,xnat-webcrash-looping on a DB password mismatch, and thefl-clientnot holding its NVFLARE connection) are fixed on this branch —Trust_K8snow connects to the stag hub and runs healthy. Remaining work before production is tracked in #593 (deployment robustness: automated kit provisioning, egress-config persistence, hub-side fl-api hardening), #516 (NetworkPolicy audit), and #530 (RBAC/PodSecurity). The kernel-7 gRPC (#527), sidecar FL-client (#528), and XNAT/Orthanc Ingress (#529) issues were triaged and closed as out-of-scope.
A K8s trust uses the Helm chart in deploy/providers/kubernetes/ and follows the same outbound-only architecture as Docker Compose trusts — no inbound ports are exposed from the cluster.
Run from the repo root. <CODE> is your trust name (e.g. Trust_K8s):
make new-trust TRUST_CODE=<CODE> TRUST_NAME="<Human Name>"
make -C deploy/providers/AWS register-trusts KIT=<CODE> PROD=stag
make sync-trust-kit KIT=<CODE> PROD=stagThis writes trust/.env.<CODE>.stag with the per-trust keys (TRUST_API_KEY, TRUST_INTERNAL_SERVICE_KEY) and the hub-shared block (AES_KEY_BASE64, CENTRAL_HUB_API_URL, FL settings). Registration is idempotent — safe to re-run.
Create deploy/providers/kubernetes/values-secrets.yaml with the secrets the kit doesn't own. The required keys are:
| Secret key | Purpose |
|---|---|
omop-postgres-password |
OMOP PostgreSQL password |
data-access-postgres-password |
Data-access reader DB password |
orthanc-registered-users |
Orthanc registered users (JSON) |
xnat-admin-password |
XNAT admin password |
xnat-service-user / xnat-service-password |
XNAT service account |
xnat-datasource-password |
XNAT PostgreSQL password |
grafana-admin-password |
Grafana admin password |
s3-access-key-id / s3-secret-access-key |
AWS credentials for the fl-client S3 kit-sync init container |
The per-trust keys (aes-key-base64, trust-api-key, trust-internal-service-key*) are patched into the cluster Secret by sync-kit in the next step — do not add them to values-secrets.yaml.
make -C deploy/providers/kubernetes sync-kit KIT=<CODE> PROD=stagThis reads trust/.env.<CODE>.stag, patches the per-trust keys into the Kubernetes Secret (trust-release-flip-trust-secrets), and writes a secret-free Helm override file deploy/providers/kubernetes/k8s-trust-<CODE>.yaml.
make -C deploy/providers/kubernetes deploy-trust-k8s KIT=<CODE> PROD=stagThis runs helm upgrade --install with the generated override and then
patch-kit-secrets (which injects the per-trust keys into the Helm-owned Secret
and restarts the API deployments). Equivalent raw Helm for the install step:
helm upgrade --install trust-release ./deploy/providers/kubernetes/ \
--namespace flip-trust --create-namespace \
-f deploy/providers/kubernetes/values.yaml \
-f deploy/providers/kubernetes/values-secrets.yaml \
-f deploy/providers/kubernetes/k8s-trust-<CODE>.yaml
⚠️ First-time / clean-install rough edges (tracked in #595):
sync-kit(step 3) creates the Secret viakubectl, which a freshhelm installcannot adopt (missing key "app.kubernetes.io/managed-by"). On a clean namespace, delete it first so Helm owns it:kubectl delete secret trust-release-flip-trust-secrets -n flip-trust.- If the
xnat-initpost-install hook fails,helmreports the release failed andpatch-kit-secretsis skipped — trust-api then polls with the stale seed key and gets401. Re-run it manually:make -C deploy/providers/kubernetes patch-kit-secrets KIT=<CODE> PROD=stag.
kubectl get pods -n flip-trust
kubectl logs -n flip-trust -l app.kubernetes.io/component=trust-api
# Expect: POST .../api/trust/heartbeat "HTTP/1.1 200 OK"
# GET .../api/tasks/pending "HTTP/1.1 200 OK"A 401 "API key is missing" means the API-key header is mismatched — check TRUST_API_KEY_HEADER in your override file.
Polling works without any firewall change. For FL training, the fl-client must reach the hub's FL server. Add the K8s node's egress IP to K8S_TRUST_PUBLIC_IPS in .env.stag (an HCL list, e.g. K8S_TRUST_PUBLIC_IPS=["1.2.3.4"]), then reconcile the NLB security group:
make -C deploy/providers/AWS add-k8s-trust K8S_TRUST_IP=<node-egress-ip> PROD=stagThis runs a normal terraform apply (no -target), so re-running with an already-listed IP is a no-op. If the node's egress IP changes, add the new one to the list and re-run.
For full configuration reference, secrets management, troubleshooting, and known limitations see deploy/providers/kubernetes/README.md.
The repository is organised as follows:
deploy: Docker Compose deployment and infrastructure files (dev/prod, flower/nvflare); AWS/Kubernetes/local providers underdeploy/providers/docs: Sphinx documentation (ReadTheDocs)flip-api: Central Hub API service (Python/FastAPI)flip-ui: Frontend UI (Vue 3 / TypeScript / TailwindCSS)flip-utils: TheflipPython package (pip-installableflip-utils) — platform logic, NVFLARE components, Flower helpersfl-services: Docker images for FL networks, per backend:fl-services/nvflare/{fl-server,fl-client,fl-api-base,fl-base}andfl-services/flower/{superlink,supernode,fl-api-flower,fl-base}. Each backend'sMakefilealso owns its network provisioning underprovision/.fl-apps: FL app templates per backend:fl-apps/nvflare/{standard,standard_client_api,evaluation,evaluation_client_api,diffusion_model,fed_opt},fl-apps/flower/{standard,evaluation}(pluscheck_required_files.sh)fl-tutorials: End-to-end tutorial examples per backend:fl-tutorials/nvflare/(xray classification, spleen seg/eval, diffusion) andfl-tutorials/flower/(xray classification, 3D spleen seg, numpy)trust: Services deployed inside each trust environmentscripts: Utility scripts (incl.check-fl-provisioned.sh— themake upFL-kit guard)
Trusts authenticate to the Central Hub using per-trust API keys. All trust communication is outbound — trusts poll the hub over HTTPS at the canonical subdomain (e.g. https://app.flip.aicentre.co.uk/api/...), which is fronted by CloudFront and proxied to the ALB. See CLAUDE.md for the full authentication model and deploy/providers/local/README.md for on-premises trust deployment.
We welcome contributions from the community. See CONTRIBUTING.md for guidance on setting up a development environment, adding new services, coding standards, testing practices, and the pull request process.
