From 033f3e16f3fc2050f854a0a21120cbdd9b2f3b5b Mon Sep 17 00:00:00 2001 From: cstns Date: Wed, 11 Mar 2026 11:09:38 +0200 Subject: [PATCH] Add Docker configuration for EMQX, Postgres, and supporting services --- .docker/emqx/acl.conf | 8 + .docker/emqx/api-keys | 1 + .docker/emqx/cluster.hocon | 306 ++++++++++++++++++++++++++++++++++++ .docker/emqx/start-emqx.sh | 7 + .docker/postgres/.gitignore | 1 + docker-compose.yml | 50 ++++++ 6 files changed, 373 insertions(+) create mode 100644 .docker/emqx/acl.conf create mode 100644 .docker/emqx/api-keys create mode 100644 .docker/emqx/cluster.hocon create mode 100755 .docker/emqx/start-emqx.sh create mode 100644 .docker/postgres/.gitignore create mode 100644 docker-compose.yml diff --git a/.docker/emqx/acl.conf b/.docker/emqx/acl.conf new file mode 100644 index 0000000000..441beebc6f --- /dev/null +++ b/.docker/emqx/acl.conf @@ -0,0 +1,8 @@ +{allow, {username, {re, "^dashboard$"}}, subscribe, ["$SYS/#"]}. + +{allow, {ipaddr, "127.0.0.1"}, all, ["$SYS/#", "#"]}. + +{deny, all, subscribe, ["$SYS/#"]}. + +{allow, all}. + diff --git a/.docker/emqx/api-keys b/.docker/emqx/api-keys new file mode 100644 index 0000000000..bb2ee1799e --- /dev/null +++ b/.docker/emqx/api-keys @@ -0,0 +1 @@ +flowforge:verySecret:administrator diff --git a/.docker/emqx/cluster.hocon b/.docker/emqx/cluster.hocon new file mode 100644 index 0000000000..e611f9e6b8 --- /dev/null +++ b/.docker/emqx/cluster.hocon @@ -0,0 +1,306 @@ +authentication = [ + { + backend = http + body { + clientId = "${clientid}" + password = "${password}" + username = "${username}" + } + connect_timeout = "15s" + enable = true + enable_pipelining = 100 + headers { + content-type = "application/json" + } + mechanism = password_based + method = post + pool_size = 8 + request_timeout = "5s" + ssl { + ciphers = [] + depth = 10 + enable = false + hibernate_after = "5s" + log_level = notice + reuse_sessions = true + secure_renegotiate = true + verify = verify_peer + versions = [ + "tlsv1.3", + "tlsv1.2" + ] + } + url = "http://host.docker.internal:3000/api/comms/v2/auth" + }, + { + backend = built_in_database + bootstrap_file = "${EMQX_ETC_DIR}/auth-built-in-db-bootstrap.csv" + bootstrap_type = plain + enable = true + mechanism = password_based + password_hash_algorithm {name = plain, salt_position = disable} + user_id_type = username + } +] +authorization { + cache { + enable = true + excludes = [] + max_size = 32 + ttl = "1m" + } + deny_action = ignore + no_match = allow + sources = [ + { + body { + action = "${action}" + topic = "${topic}" + username = "${username}" + } + connect_timeout = "15s" + enable = true + enable_pipelining = 100 + headers { + content-type = "application/json" + } + method = post + pool_size = 8 + request_timeout = "30s" + ssl { + ciphers = [] + depth = 10 + enable = false + hibernate_after = "5s" + log_level = notice + reuse_sessions = true + secure_renegotiate = true + verify = verify_peer + versions = [ + "tlsv1.3", + "tlsv1.2" + ] + } + type = http + url = "http://host.docker.internal:3000/api/comms/v2/acls" + }, + { + enable = false + path = "data/authz/acl.conf" + type = file + } + ] +} +listeners { + ssl { + default { + acceptors = 16 + access_rules = [ + "allow all" + ] + bind = "0.0.0.0:8883" + enable = true + enable_authn = true + max_conn_rate = infinity + max_connections = infinity + mountpoint = "" + proxy_protocol = false + proxy_protocol_timeout = "3s" + ssl_options { + cacertfile = "${EMQX_ETC_DIR}/certs/cacert.pem" + certfile = "${EMQX_ETC_DIR}/certs/cert.pem" + ciphers = [] + client_renegotiation = true + depth = 10 + enable_crl_check = false + fail_if_no_peer_cert = false + gc_after_handshake = false + handshake_timeout = "15s" + hibernate_after = "5s" + honor_cipher_order = true + keyfile = "${EMQX_ETC_DIR}/certs/key.pem" + log_level = notice + ocsp { + enable_ocsp_stapling = false + refresh_http_timeout = "15s" + refresh_interval = "5m" + } + reuse_sessions = true + secure_renegotiate = true + verify = verify_none + versions = [ + "tlsv1.3", + "tlsv1.2" + ] + } + tcp_options { + active_n = 100 + backlog = 1024 + buffer = "4KB" + high_watermark = "1MB" + keepalive = none + nodelay = true + reuseaddr = true + send_timeout = "15s" + send_timeout_close = true + } + zone = default + } + } + tcp { + default { + acceptors = 16 + access_rules = [ + "allow all" + ] + bind = "0.0.0.0:1883" + enable = true + enable_authn = true + max_conn_rate = infinity + max_connections = infinity + mountpoint = "${client_attrs.team}" + proxy_protocol = false + proxy_protocol_timeout = "3s" + tcp_options { + active_n = 100 + backlog = 1024 + buffer = "4KB" + high_watermark = "1MB" + keepalive = none + nodelay = true + reuseaddr = true + send_timeout = "15s" + send_timeout_close = true + } + zone = default + } + } + ws { + default { + acceptors = 16 + access_rules = [ + "allow all" + ] + bind = "0.0.0.0:8083" + enable = true + enable_authn = true + max_conn_rate = infinity + max_connections = infinity + mountpoint = "${client_attrs.team}" + proxy_protocol = false + proxy_protocol_timeout = "3s" + tcp_options { + active_n = 100 + backlog = 1024 + buffer = "4KB" + high_watermark = "1MB" + keepalive = none + nodelay = true + reuseaddr = true + send_timeout = "15s" + send_timeout_close = true + } + websocket { + allow_origin_absence = true + check_origin_enable = false + check_origins = "http://host.docker.internal:18083, http://127.0.0.1:18083" + compress = false + deflate_opts { + client_context_takeover = takeover + client_max_window_bits = 15 + mem_level = 8 + server_context_takeover = takeover + server_max_window_bits = 15 + strategy = default + } + fail_if_no_subprotocol = true + idle_timeout = "7200s" + max_frame_size = infinity + mqtt_path = "/" + mqtt_piggyback = multiple + proxy_address_header = "x-forwarded-for" + proxy_port_header = "x-forwarded-port" + supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" + validate_utf8 = true + } + zone = default + } + } + wss { + default { + acceptors = 16 + access_rules = [ + "allow all" + ] + bind = "0.0.0.0:8084" + enable = true + enable_authn = true + max_conn_rate = infinity + max_connections = infinity + mountpoint = "${client_attrs.team}" + proxy_protocol = false + proxy_protocol_timeout = "3s" + ssl_options { + cacertfile = "${EMQX_ETC_DIR}/certs/cacert.pem" + certfile = "${EMQX_ETC_DIR}/certs/cert.pem" + ciphers = [] + client_renegotiation = true + depth = 10 + fail_if_no_peer_cert = false + handshake_timeout = "15s" + hibernate_after = "5s" + honor_cipher_order = true + keyfile = "${EMQX_ETC_DIR}/certs/key.pem" + log_level = notice + reuse_sessions = true + secure_renegotiate = true + verify = verify_none + versions = [ + "tlsv1.3", + "tlsv1.2" + ] + } + tcp_options { + active_n = 100 + backlog = 1024 + buffer = "4KB" + high_watermark = "1MB" + keepalive = none + nodelay = true + reuseaddr = true + send_timeout = "15s" + send_timeout_close = true + } + websocket { + allow_origin_absence = true + check_origin_enable = false + check_origins = "http://host.docker.internal:18083, http://127.0.0.1:18083" + compress = false + deflate_opts { + client_context_takeover = takeover + client_max_window_bits = 15 + mem_level = 8 + server_context_takeover = takeover + server_max_window_bits = 15 + strategy = default + } + fail_if_no_subprotocol = true + idle_timeout = "7200s" + max_frame_size = infinity + mqtt_path = "/" + mqtt_piggyback = multiple + proxy_address_header = "x-forwarded-for" + proxy_port_header = "x-forwarded-port" + supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" + validate_utf8 = true + } + zone = default + } + } +} +dashboard { + default_password = topSecret +} +api_key { + bootstrap_file = "/mounted/config/api-keys" +} diff --git a/.docker/emqx/start-emqx.sh b/.docker/emqx/start-emqx.sh new file mode 100755 index 0000000000..95fad1d127 --- /dev/null +++ b/.docker/emqx/start-emqx.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +docker run -d -v `pwd`/cluster.hocon:/opt/emqx/data/configs/cluster.hocon \ + --restart=unless-stopped \ + -v `pwd`/api-keys:/mounted/config/api-keys \ + -v `pwd`/acl.conf:/opt/emqx/data/authz/acl.conf \ + -p 1883:1883 -p 8083:8083 -p 18083:18083 --name emqx emqx/emqx:5.8 diff --git a/.docker/postgres/.gitignore b/.docker/postgres/.gitignore new file mode 100644 index 0000000000..8fce603003 --- /dev/null +++ b/.docker/postgres/.gitignore @@ -0,0 +1 @@ +data/ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000000..ef317a48d0 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +name: flowfuse +services: + postgres: + image: postgres:latest + environment: + POSTGRES_USER: ff + POSTGRES_PASSWORD: super-secret + POSTGRES_DB: flowfuse + volumes: + - ./.docker/postgres/data:/var/.data/postgres + ports: + - "5432:5432" + + nginx: + network_mode: host + image: nginx:latest + container_name: nginx-proxy + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/conf.d:/etc/nginx/conf.d:ro + - ./nginx/certs:/etc/nginx/certs:ro + extra_hosts: + - "host.docker.internal:host-gateway" + + emqx: + image: emqx/emqx:5.8 + container_name: emqx + restart: unless-stopped + ports: + - "1883:1883" + - "8083:8083" + - "18083:18083" + extra_hosts: + - "host.docker.internal:host-gateway" + volumes: + - ./.docker/emqx/cluster.hocon:/opt/emqx/data/configs/cluster.hocon + - ./.docker/emqx/api-keys:/mounted/config/api-keys + - ./.docker/emqx/acl.conf:/opt/emqx/data/authz/acl.conf + + mailpit: + image: axllent/mailpit + ports: + - "8020:8025" + - "1020:1025" + environment: + MP_MAX_MESSAGES: "5000" + MP_SMTP_AUTH_ACCEPT_ANY: "1" + MP_SMTP_AUTH_ALLOW_INSECURE: "1"