Local Demo
The best way to test InterDiode is to use it locally with Docker Compose, using the compose.yaml file below.
It provides a minimal installation of two servers with a unidirectional UDP data transfer.
The black instance (the one normally connected to the internet) will be available at http://localhost:8881/,
the red instance (normally on the internal network) will be available at http://localhost:8882/.
InterDiode provides Docker/OCI images that are available in Docker Hub. It is possible to always use the latest stable tag or to use another service that handles updating Docker images.
This guide explains the setup based on docker-compose, but the installation of docker-compose itself is out of scope of this documentation. To do this, please follow the official install instructions.
Only Docker and docker-compose are required to be installed on the host machine. The example below demonstrates downloading a free license, but you can use any valid InterDiode license.
export EMAIL=<enter_your_email_here>
export NAME=<enter_your_name_here>
# download a license tied to your email and name, and store it in the compose.env file
echo "LICENSE_KEY=$(curl -L -X POST -H 'Content-Type: application/x-www-form-urlencoded' --data-urlencode product_id=42 --data-urlencode buyer_name=${NAME} --data-urlencode buyer_email=${EMAIL} https://license.interdiode.fr/v1/license/)" > compose.env
# download the compose.yaml file
curl -L -O https://interdiode.fr/static/latest/_static/examples/compose.yaml
# start the demo
docker compose up -d
Next Steps
Once all the containers are running, you can access the web interfaces of both instances at http://localhost:8881/ for the black instance and http://localhost:8882/ for the red instance.
Access to the black instance and create a new account with the “Create account” button. Then, log in with the newly created account.
In the “Administration” section, add the license file that you obtained from the InterDiode team. The license will be automatically applied to the red instance.
Access to the red instance and create a new account with the “Create account” button. Then, log in with the newly created account.
services:
background_black:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 background
depends_on:
db_black:
condition: service_healthy
mailpit_black:
condition: service_healthy
migrate_black:
condition: service_completed_successfully
redis_black:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_black:5432/interdiode_db
- EMAIL_FROM=admin-black@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_black:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=black
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8081
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_black/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8881/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-background.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/black:/data/files
- ./caches/black:/data/caches
background_red:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 background
depends_on:
db_red:
condition: service_healthy
mailpit_red:
condition: service_healthy
migrate_red:
condition: service_completed_successfully
redis_red:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_red:5432/interdiode_db
- EMAIL_FROM=admin-red@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_red:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=red
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8082
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_red/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8882/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-background.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/red:/data/files
- ./caches/red:/data/caches
beat_black:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 beat
depends_on:
db_black:
condition: service_healthy
mailpit_black:
condition: service_healthy
migrate_black:
condition: service_completed_successfully
redis_black:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_black:5432/interdiode_db
- EMAIL_FROM=admin-black@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_black:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=black
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8081
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_black/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8881/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-beat.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/black:/data/files
- ./caches/black:/data/caches
beat_red:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 beat
depends_on:
db_red:
condition: service_healthy
mailpit_red:
condition: service_healthy
migrate_red:
condition: service_completed_successfully
redis_red:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_red:5432/interdiode_db
- EMAIL_FROM=admin-red@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_red:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=red
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8082
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_red/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8882/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-beat.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/red:/data/files
- ./caches/red:/data/caches
db_black:
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
- POSTGRES_DB=interdiode_db
- POSTGRES_PASSWORD=my-postgres-password
- POSTGRES_USER=interdiode
- PGDATA=/var/lib/postgresql/data/postgresql
healthcheck:
interval: 1s
retries: 10
test: [CMD, pg_isready, -U, interdiode, -d, interdiode_db]
timeout: 1s
image: postgres:18
restart: always
volumes:
- ./postgres/black:/var/lib/postgresql/data/postgresql
db_red:
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
- POSTGRES_DB=interdiode_db
- POSTGRES_PASSWORD=my-postgres-password
- POSTGRES_USER=interdiode
- PGDATA=/var/lib/postgresql/data/postgresql
healthcheck:
interval: 1s
retries: 10
test: [CMD, pg_isready, -U, interdiode, -d, interdiode_db]
timeout: 1s
image: postgres:18
restart: always
volumes:
- ./postgres/red:/var/lib/postgresql/data/postgresql
mailpit_black:
environment:
- MP_DATABASE=/tmp/mailpit.db
- MP_DISABLE_VERSION_CHECK=1
- MP_DISABLE_WAL=1
- MP_MAX_MESSAGES=5000
- MP_POP3_AUTH=admin_user:password user:password black_user:password red_user:password interdiode:password
- MP_SMTP_AUTH=admin_user:password user:password black_user:password red_user:password interdiode:password
- MP_SMTP_AUTH_ALLOW_INSECURE=1
healthcheck:
interval: 1s
retries: 10
test: [CMD, wget, --spider, -q, 'http://localhost:8025/']
timeout: 1s
image: axllent/mailpit
ports:
- 127.0.0.1:8981:8025
restart: always
mailpit_red:
environment:
- MP_DATABASE=/tmp/mailpit.db
- MP_DISABLE_VERSION_CHECK=1
- MP_DISABLE_WAL=1
- MP_MAX_MESSAGES=5000
- MP_POP3_AUTH=admin_user:password user:password black_user:password red_user:password interdiode:password
- MP_SMTP_AUTH=admin_user:password user:password black_user:password red_user:password interdiode:password
- MP_SMTP_AUTH_ALLOW_INSECURE=1
healthcheck:
interval: 1s
retries: 10
test: [CMD, wget, --spider, -q, 'http://localhost:8025/']
timeout: 1s
image: axllent/mailpit
ports:
- 127.0.0.1:8982:8025
restart: always
migrate_black:
command: interdiode-ctl configuration apply --no-input
depends_on:
db_black:
condition: service_healthy
mailpit_black:
condition: service_healthy
redis_black:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_black:5432/interdiode_db
- EMAIL_FROM=admin-black@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_black:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=black
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8081
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_black/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8881/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
image: interdiode/interdiode:2026.6.13
volumes:
- ./files/black:/data/files
- ./caches/black:/data/caches
migrate_red:
command: interdiode-ctl configuration apply --no-input
depends_on:
db_red:
condition: service_healthy
mailpit_red:
condition: service_healthy
redis_red:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_red:5432/interdiode_db
- EMAIL_FROM=admin-red@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_red:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=red
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8082
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_red/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8882/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
image: interdiode/interdiode:2026.6.13
volumes:
- ./files/red:/data/files
- ./caches/red:/data/caches
receive_red:
command: interdiode-ctl hairgap --prometheus-bind 0.0.0.0 --prometheus-port 9000 receive
depends_on:
db_red:
condition: service_healthy
mailpit_red:
condition: service_healthy
migrate_red:
condition: service_completed_successfully
redis_red:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_red:5432/interdiode_db
- EMAIL_FROM=admin-red@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_red:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=red
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8082
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_red/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8882/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-hairgap.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/red:/data/files
- ./caches/red:/data/caches
redis_black:
command: redis-server --requirepass 'my-redis-password' --save 60 1 --loglevel warning
healthcheck:
interval: 1s
retries: 10
test: [CMD, redis-cli, ping]
timeout: 1s
image: redis:alpine
restart: always
redis_red:
command: redis-server --requirepass 'my-redis-password' --save 60 1 --loglevel warning
healthcheck:
interval: 1s
retries: 10
test: [CMD, redis-cli, ping]
timeout: 1s
image: redis:alpine
restart: always
send_black:
command: interdiode-ctl hairgap --prometheus-bind 0.0.0.0 --prometheus-port 9000 send
depends_on:
db_black:
condition: service_healthy
mailpit_black:
condition: service_healthy
migrate_black:
condition: service_completed_successfully
redis_black:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_black:5432/interdiode_db
- EMAIL_FROM=admin-black@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_black:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=black
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8081
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_black/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8881/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-hairgap.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
restart: always
volumes:
- ./files/black:/data/files
- ./caches/black:/data/caches
web_black:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 http
depends_on:
db_black:
condition: service_healthy
mailpit_black:
condition: service_healthy
migrate_black:
condition: service_completed_successfully
redis_black:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_black:5432/interdiode_db
- EMAIL_FROM=admin-black@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_black:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=black
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8081
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_black/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8881/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-http.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
ports:
- 127.0.0.1:8881:8081
restart: always
volumes:
- ./files/black:/data/files
- ./caches/black:/data/caches
web_red:
command: interdiode-ctl run --prometheus-bind 0.0.0.0 --prometheus-port 9000 http
depends_on:
db_red:
condition: service_healthy
mailpit_red:
condition: service_healthy
migrate_red:
condition: service_completed_successfully
redis_red:
condition: service_healthy
env_file:
- ./compose.env
environment:
- ALLOW_LOCAL_USERS=true
- ALLOW_USER_CREATION=true
- DATABASE_URL=postgresql://interdiode:my-postgres-password@db_red:5432/interdiode_db
- EMAIL_FROM=admin-red@interdiode.example.com
- EMAIL_HOST_URL=smtp://interdiode:password@mailpit_red:1025
- HTTP_REMOTE_GROUPS_HEADER=
- HTTP_REMOTE_USER_HEADER=
- INTERDIODE_MODE=red
- LANGUAGE_CODE=en-us
- LISTEN_ADDRESS=0.0.0.0:8082
- LOG_DIR=
- LOG_LEVEL=debug
- REDIS_URL=redis://:my-redis-password@redis_red/1
- RED_DESTINATION_IP=receive_red
- RED_DESTINATION_PORT=9091
- REMOTE_USER_DEFAULT_GROUPS=
- REQUIRE_NEW_USER_VALIDATION=false
- SERVER_BASE_URL=http://localhost:8882/
- SHARED_TRANSFER_KEY=secret_key
- TRANSFER_MODE=udp
- WORKER_PROCESSES=4
healthcheck:
interval: 1s
retries: 10
test: [CMD, python, -c, 'import os; fd = open(''/data/run/interdiode-http.pid''); pid=fd.read().strip() ; os.kill(int(pid), 0); fd.close()']
timeout: 1s
image: interdiode/interdiode:2026.6.13
ports:
- 127.0.0.1:8882:8082
restart: always
volumes:
- ./files/red:/data/files
- ./caches/red:/data/caches