├── .env.example ├── .github ├── dependabot.yml └── workflows │ ├── build-containers.yml │ ├── project-automation.yml │ └── test-docker-compose.yaml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── UPGRADE.md ├── broker └── mosquitto.conf ├── build-containers.sh ├── docker-compose-local-dev.yml ├── docker-compose-quick-start.yml ├── docker-compose.yml ├── etc ├── flowforge-storage.yml └── flowforge.yml ├── file-server ├── Dockerfile ├── README.md └── package.json ├── flowforge-docker-local-dev ├── Dockerfile └── package.json ├── flowforge-docker ├── Dockerfile ├── install-device-cache.sh └── package.json ├── nginx └── my-proxy.conf ├── node-red-container ├── Dockerfile ├── healthcheck.js └── package.json ├── npm ├── README.md ├── conf │ └── config.yaml └── online │ ├── .gitignore │ ├── conf │ └── config.yaml │ ├── populate.bat │ ├── populate.sh │ └── temp │ └── package.json ├── setup-context-db.sh └── setup-db.sh /.env.example: -------------------------------------------------------------------------------- 1 | DOMAIN= 2 | APPLICATION_DOMAIN="" 3 | 4 | ### TLS certificates configuration 5 | TLS_ENABLED="" 6 | TLS_CERTIFICATE="" 7 | TLS_KEY="" 8 | APP_TLS_CERTIFICATE="" 9 | APP_TLS_KEY="" 10 | 11 | ### Database configuration 12 | ### Fill only if you want to use an external database (https://flowfuse.com/docs/install/docker/#how-to-use-external-database-server%3F), leave empty otherwise 13 | DB_HOST="" 14 | DB_USER="" 15 | DB_PASSWORD="" 16 | 17 | ### Email configuration 18 | EMAIL_ENABLED=false 19 | EMAIL_HOST="" 20 | EMAIL_PORT=587 21 | EMAIL_SECURE="" 22 | EMAIL_USER="" 23 | EMAIL_PASSWORD="" 24 | 25 | ### Docker Driver options 26 | DOCKER_DRIVER_PRIVATE_CA_PATH="" 27 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build-containers.yml: -------------------------------------------------------------------------------- 1 | name: Build and push containers 2 | on: 3 | push: 4 | tags: 5 | - "v*.*.*" 6 | 7 | jobs: 8 | build_application_container: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 12 | with: 13 | repository: 'flowforge/docker-compose' 14 | fetch-depth: 0 15 | path: 'docker-compose' 16 | - name: Docker Meta Data 17 | id: meta 18 | uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0 19 | with: 20 | tags: | 21 | type=semver,event=tag,pattern={{version}} 22 | flavor: | 23 | latest=true 24 | images: | 25 | flowforge/forge-docker 26 | flowfuse/forge-docker 27 | - name: Setup QEMU 28 | uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 29 | - name: Setup Docker buildx 30 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0 31 | - name: docker login 32 | uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 33 | with: 34 | username: flowforge 35 | password: ${{ secrets.DOCKER_HUB_PASSWORD }} 36 | - name: Build and push FlowForge Application container 37 | uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 38 | with: 39 | context: docker-compose/flowforge-docker 40 | file: docker-compose/flowforge-docker/Dockerfile 41 | platforms: linux/amd64, linux/arm64, linux/arm/v7 42 | tags: ${{ steps.meta.outputs.tags }} 43 | push: true 44 | - name: Push README 45 | uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2 46 | with: 47 | repository: flowforge/forge-docker 48 | username: flowforge 49 | password: ${{ secrets.DOCKER_HUB_PASSWORD }} 50 | readme-filepath: docker-compose/README.md 51 | - name: Push README flowfuse 52 | uses: peter-evans/dockerhub-description@432a30c9e07499fd01da9f8a49f0faf9e0ca5b77 # v4.0.2 53 | with: 54 | repository: flowfuse/forge-docker 55 | username: flowfuse 56 | password: ${{ secrets.DOCKER_HUB_PASSWORD_FLOWFUSE }} 57 | readme-filepath: docker-compose/README.md 58 | 59 | release-compose-files: 60 | runs-on: ubuntu-latest 61 | permissions: 62 | contents: write 63 | steps: 64 | - name: Checkout 65 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 66 | with: 67 | repository: 'flowforge/docker-compose' 68 | 69 | - name: Add assets to the release 70 | uses: softprops/action-gh-release@da05d552573ad5aba039eaac05058a918a7bf631 # v2.2.2 71 | with: 72 | files: | 73 | docker-compose.yml 74 | docker-compose-quick-start.yml 75 | -------------------------------------------------------------------------------- /.github/workflows/project-automation.yml: -------------------------------------------------------------------------------- 1 | name: Project automations 2 | on: 3 | issues: 4 | types: 5 | - opened 6 | jobs: 7 | add_to_product_board: 8 | uses: flowfuse/.github/.github/workflows/project-automation.yml@main 9 | secrets: 10 | token: ${{ secrets.PROJECT_ACCESS_TOKEN }} 11 | -------------------------------------------------------------------------------- /.github/workflows/test-docker-compose.yaml: -------------------------------------------------------------------------------- 1 | name: Test Docker Compose 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - '!release-*' 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | 13 | jobs: 14 | wait-for-containers-build: 15 | name: Wait for container images build 16 | runs-on: ubuntu-latest 17 | if: | 18 | !startsWith(github.head_ref, 'release-') 19 | steps: 20 | - name: Generate a token 21 | id: generate_token 22 | uses: tibdex/github-app-token@3beb63f4bd073e61482598c45c71c1019b59b73a # v2.1.0 23 | with: 24 | app_id: ${{ secrets.GH_BOT_APP_ID }} 25 | private_key: ${{ secrets.GH_BOT_APP_KEY }} 26 | 27 | - name: Checkout 28 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | token: ${{ steps.generate_token.outputs.token }} 31 | 32 | - name: Wait for container images build 33 | run: | 34 | while :; do 35 | result=$(gh api repos/:owner/:repo/actions/workflows | jq -r '.workflows[] | select(.name=="Build and push containers") | .id' | xargs -I {} gh api repos/:owner/:repo/actions/workflows/{}/runs --jq '.workflow_runs | max_by(.run_number)') 36 | status=$(echo "$result" | jq -r '.status') 37 | conclusion=$(echo "$result" | jq -r '.conclusion') 38 | if [[ "$status" == "completed" ]]; then 39 | if [[ "$conclusion" == "success" ]]; then 40 | echo "Build and push containers workflow completed successfully" 41 | break 42 | else 43 | echo "Build and push containers workflow failed" 44 | exit 1 45 | fi 46 | elif [[ "$status" == "in_progress" ]]; then 47 | echo "Build and push containers workflow is still running" 48 | sleep 60 49 | else 50 | echo "Build and push containers workflow returned unexpected status: $status" 51 | exit 1 52 | fi 53 | done 54 | env: 55 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 56 | 57 | default-stack: 58 | name: Test default stack 59 | needs: wait-for-containers-build 60 | runs-on: ubuntu-latest 61 | steps: 62 | - name: Checkout 63 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 64 | 65 | - name: Create .env file for default settings 66 | run: | 67 | cp .env.example .env 68 | sed -i 's/DOMAIN=.*/DOMAIN=ci-example.com/' .env 69 | 70 | - name: Create stack 71 | uses: hoverkraft-tech/compose-action@8be2d741e891ac9b8ac20825e6f3904149599925 # v2.2.0 72 | with: 73 | compose-file: "./docker-compose.yml" 74 | up-flags: "-d --quiet-pull" 75 | 76 | - name: Check readiness 77 | run: | 78 | has_healthcheck() { 79 | local container=$1 80 | local health_status=$(docker inspect --format='{{if .Config.Healthcheck}}true{{else}}false{{end}}' "$container") 81 | [ "$health_status" = "true" ] 82 | } 83 | 84 | check_containers() { 85 | containers=$(docker compose ps -q) 86 | for container in $containers; do 87 | container_name=$(docker inspect --format '{{.Name}}' "$container" | sed 's/\///') 88 | container_ip=$(docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") 89 | 90 | if has_healthcheck "$container"; then 91 | echo "Container has healthcheck defined" 92 | status=$(docker inspect --format "{{.State.Health.Status}}" "$container") 93 | if [ "$status" != "healthy" ]; then 94 | echo "❌ Container $container_name is not healthy (status: $status)" 95 | return 1 96 | fi 97 | else 98 | running=$(docker inspect --format "{{.State.Running}}" "$container") 99 | if [ "$running" != "true" ]; then 100 | echo "❌ Container $container_name is not running" 101 | return 1 102 | fi 103 | fi 104 | 105 | echo "✅ Container $container_name is ready" 106 | done 107 | return 0 108 | } 109 | 110 | # Wait for containers with timeout 111 | TIMEOUT=300 # 5 minutes timeout 112 | ELAPSED=0 113 | SLEEP_TIME=10 114 | 115 | until check_containers; do 116 | if [ $ELAPSED -ge $TIMEOUT ]; then 117 | echo "❌ Timeout waiting for containers to be ready" 118 | docker compose ps 119 | docker compose logs 120 | exit 1 121 | fi 122 | echo "⏳ Waiting for containers... ($ELAPSED seconds elapsed)" 123 | sleep $SLEEP_TIME 124 | ELAPSED=$((ELAPSED + SLEEP_TIME)) 125 | done 126 | 127 | echo "✅ All containers are ready!" 128 | docker compose ps 129 | 130 | - name: Tear down the stack 131 | if: always() 132 | run: docker compose down 133 | 134 | 135 | quick-start-stack: 136 | name: Test quick-start stack 137 | needs: wait-for-containers-build 138 | runs-on: ubuntu-latest 139 | steps: 140 | - name: Checkout 141 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 142 | 143 | - name: Create .env file for default settings 144 | run: | 145 | cp .env.example .env 146 | sed -i 's/DOMAIN=.*/DOMAIN=ci-example.com/' .env 147 | 148 | - name: Create stack 149 | uses: hoverkraft-tech/compose-action@8be2d741e891ac9b8ac20825e6f3904149599925 # v2.2.0 150 | with: 151 | compose-file: "./docker-compose-quick-start.yml" 152 | up-flags: "-d --quiet-pull" 153 | 154 | - name: Check readiness 155 | run: | 156 | has_healthcheck() { 157 | local container=$1 158 | local health_status=$(docker inspect --format='{{if .Config.Healthcheck}}true{{else}}false{{end}}' "$container") 159 | [ "$health_status" = "true" ] 160 | } 161 | 162 | check_containers() { 163 | containers=$(docker compose ps -q) 164 | for container in $containers; do 165 | container_name=$(docker inspect --format '{{.Name}}' "$container" | sed 's/\///') 166 | container_ip=$(docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$container") 167 | 168 | if has_healthcheck "$container"; then 169 | echo "Container has healthcheck defined" 170 | status=$(docker inspect --format "{{.State.Health.Status}}" "$container") 171 | if [ "$status" != "healthy" ]; then 172 | echo "❌ Container $container_name is not healthy (status: $status)" 173 | return 1 174 | fi 175 | else 176 | running=$(docker inspect --format "{{.State.Running}}" "$container") 177 | if [ "$running" != "true" ]; then 178 | echo "❌ Container $container_name is not running" 179 | return 1 180 | fi 181 | fi 182 | 183 | echo "✅ Container $container_name is ready" 184 | done 185 | return 0 186 | } 187 | 188 | # Wait for containers with timeout 189 | TIMEOUT=300 # 5 minutes timeout 190 | ELAPSED=0 191 | SLEEP_TIME=10 192 | 193 | until check_containers; do 194 | if [ $ELAPSED -ge $TIMEOUT ]; then 195 | echo "❌ Timeout waiting for containers to be ready" 196 | docker compose ps 197 | docker compose logs 198 | exit 1 199 | fi 200 | echo "⏳ Waiting for containers... ($ELAPSED seconds elapsed)" 201 | sleep $SLEEP_TIME 202 | ELAPSED=$((ELAPSED + SLEEP_TIME)) 203 | done 204 | 205 | echo "✅ All containers are ready!" 206 | docker compose ps 207 | 208 | - name: Tear down the stack 209 | if: always() 210 | run: docker compose down 211 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | db 2 | fileStorage 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | #### 2.18.0: Release 2 | 3 | - Bump docker/build-push-action from 6.16.0 to 6.18.0 (#209) 4 | 5 | #### 2.17.0: Release 6 | 7 | - Bump docker/build-push-action from 6.15.0 to 6.16.0 (#206) 8 | - Bump softprops/action-gh-release from 2.2.1 to 2.2.2 (#205) 9 | 10 | #### 2.16.0: Release 11 | 12 | - Bump peter-evans/dockerhub-description from 4.0.0 to 4.0.2 (#202) 13 | - Postgres healthcheck should be run as root not postgres user (#201) 14 | - Add git command line tool (#199) @hardillb 15 | - fix: Set proper permissions for initialization scripts (#200) @ppawlowski 16 | - chore: Pin external actions to commit hash (#196) @ppawlowski 17 | - update device cache (#195) @hardillb 18 | 19 | #### 2.15.0: Release 20 | 21 | - feat: Enable storage for Node-RED intances (#193) @ppawlowski 22 | - ci: Remove `Resync Maintenance` step from the pipeline (#192) @ppawlowski 23 | 24 | #### 2.14.1: Release 25 | 26 | 27 | #### 2.14.0: Release 28 | 29 | - Bump hoverkraft-tech/compose-action from 2.0.2 to 2.2.0 (#189) 30 | 31 | #### 2.13.1: Release 32 | 33 | - Bump for 2.13.1 Flowfuse Release @hardillb 34 | 35 | #### 2.13.0: Release 36 | 37 | - fix: Clarify `DB_*` entries in `.env.example` file (#185) @ppawlowski 38 | - ci: Wait for containers to build before testing docker compose (#184) @ppawlowski 39 | - Add NR 4.0.8 to cache (#183) @hardillb 40 | 41 | #### 2.12.0: Release 42 | 43 | - Add node-red editor cache files (#181) 44 | - Update docker-compose.yml (#178) 45 | - feat: Add healthchecks for services in Docker Compose (#167) 46 | - Bump max MQTT packet to 128mb (#176) 47 | - Fix EMQX WS port number (#175) 48 | - Update Node-RED Dockerfile to 3.1.15 (#180) @hardillb 49 | - ci: Do not run test against `release-*` branches (#177) @ppawlowski 50 | - ci: Add Docker Compose files as assets to a release (#179) @ppawlowski 51 | - feat: Add possibility to run application on custom domain name (#172) @ppawlowski 52 | - docs: Adjust UPGRADE instruction (#170) @ppawlowski 53 | 54 | #### 2.11.0: Release 55 | 56 | - Ensure the broker service name doesn't change (#168) 57 | - ci: Introduce workflow for testing docker compose (#166) 58 | - Update Node-RED version in Docker compose (#162) 59 | - chore: Refactor docker-compose to simplify installation experience (#160) 60 | - First pass at TeamBroker (#165) @hardillb 61 | - fix: Remove interpolation when creating TLS certificates (#164) @ppawlowski 62 | 63 | #### 2.10.0: Release 64 | 65 | - Introduce quick-start compose file (#158) @ppawlowski 66 | 67 | #### 2.9.0: Release 68 | 69 | - Bump Node-RED base container (#156) @hardillb 70 | - Bump docker/build-push-action from 2 to 6 (#144) @app/dependabot 71 | - Bump docker/metadata-action from 3 to 5 (#143) @app/dependabot 72 | - Bump docker/setup-qemu-action from 1 to 3 (#140) @app/dependabot 73 | - Bump docker/login-action from 1 to 3 (#139) @app/dependabot 74 | - Bump docker/setup-buildx-action from 1 to 3 (#136) @app/dependabot 75 | 76 | #### 2.8.0: Release 77 | 78 | - Update docker-compose.yml (#154) @hardillb 79 | 80 | #### 2.7.1: Release 81 | 82 | - Bump to driver-docker 2.7.1 @knolleary 83 | 84 | #### 2.7.0: Release 85 | 86 | - Lock nginx-proxy to a fixed tag (#151) @hardillb 87 | - Add mount for persistent-storage (#150) @hardillb 88 | - rebrand working dir (#149) @hardillb 89 | 90 | #### 2.6.1: Release 91 | 92 | - Bump to FlowFuse 2.6.1 @hardillb 93 | 94 | #### 2.6.0: Release 95 | 96 | - Add storage path to /data (#145) @hardillb 97 | - Bump peter-evans/dockerhub-description from 3 to 4 (#142) @app/dependabot 98 | 99 | #### 2.5.0: Release 100 | 101 | - Bump actions/checkout from 3 to 4 (#137) @app/dependabot 102 | - Enable dependabot for github actions (#135) @ppawlowski 103 | 104 | #### 2.4.0: Release 105 | 106 | 107 | #### 2.3.0: Release 108 | 109 | - Ensure ssl-certs dir exists (#131) @hardillb 110 | 111 | #### 2.2.2: Release 112 | 113 | - bump driver-docker to 2.2.1 @hardillb 114 | 115 | #### 2.2.1: Release 116 | 117 | 118 | #### 2.2.0: Release 119 | 120 | 121 | #### 2.1.1: Release 122 | 123 | - Rename containers in README and build script (#125) @hardillb 124 | - Support npm offline for windows (#126) @hardillb 125 | - NPM Registry cache for offline installs. (#121) @hardillb 126 | - Rename containers 2 (#117) @hardillb 127 | - Fix sync to maintenance branch (#123) @hardillb 128 | 129 | #### 2.1.0: Release 130 | 131 | 132 | #### 2.0.1: Release 133 | 134 | - Update versions to 2.0.1 @hardillb 135 | 136 | #### 2.0.0: Release 137 | 138 | - Push to both container names (#116) @hardillb 139 | 140 | #### 1.15.0: Release 141 | 142 | - Update to new flowfuse npm scope (#113) @knolleary 143 | - Update containers to NodeJS 18 base (#111) @hardillb 144 | 145 | #### 1.14.1: Release 146 | 147 | - Update file server and driver-docker npm references (#110) @knolleary 148 | 149 | #### 1.14.0: Release 150 | 151 | - Update to @flowfuse/nr-launcher (#108) @knolleary 152 | 153 | #### 1.13:1: Release 154 | 155 | - Bump to FlowFuse v1.13.1 @hardillb 156 | 157 | #### 1.13.0: Release 158 | 159 | 160 | #### 1.12.2: Release 161 | 162 | - Bump to FlowForge v1.12.2 @hardillb 163 | 164 | #### 1.12.1: Release 165 | 166 | - Bump to FlowForge v1.12.1 @hardillb 167 | 168 | #### 1.12.0: Release 169 | 170 | - ensure acme-companion restarts on failure (#100) @hardillb 171 | - Add note about docker-compose version (#99) @hardillb 172 | - Add step to end of release action to reset maintenance (#98) @hardillb 173 | 174 | #### 1.11.0: Release 175 | 176 | 177 | #### 1.10.1: Release 178 | 179 | - Bump to FlowForge v1.10.1 180 | 181 | #### 1.10.0: Release 182 | 183 | - Add config to set max body size (#93) @hardillb 184 | 185 | #### 1.9.0: Release 186 | 187 | 188 | #### 1.8.0: Release 189 | 190 | 191 | #### 1.7.0: Release 192 | 193 | - Update file-server version (#82) @hardillb 194 | 195 | #### 1.6.0: Release 196 | 197 | 198 | #### 1.5.0: Release 199 | 200 | 201 | #### 1.4.0: Release 202 | 203 | 204 | #### 1.3.0: Release 205 | 206 | - Only create DB if it doesn't exist (#75) @hardillb 207 | - Ensure DB setup scripts run in the correct order (#74) @hardillb 208 | 209 | #### 1.2.0: Release 210 | 211 | - Set quota defaults (#70) @hardillb 212 | - Add Context store to docker compose (#66) @hardillb 213 | - No need to symlink python anymore (#67) @hardillb 214 | 215 | #### 1.1.0: Release 216 | 217 | - Setup File Server (#51) @hardillb 218 | - First pass adding LetsEncrypt support (#42) @hardillb 219 | - Fix path to README.md for docker hub publish (#50) @hardillb 220 | 221 | #### 1.0.1: Release 222 | 223 | - Update to FlowForge 1.0.1 224 | 225 | #### 1.0.0: Release 226 | 227 | - Remove FlowForge nodes from /data/package.json (#46) @hardillb 228 | - Pin to Postgres 14 (#44) @hardillb 229 | - Add step to the build action to push README.md (#41) @hardillb 230 | 231 | #### 0.10.0: Release 232 | 233 | - Fix case in npm authToken (#37) @hardillb 234 | - Set default python location for npm (#36) @hardillb 235 | - Add build tag when installing nr-launcher (#35) @hardillb 236 | - First pass at GH Action to build docker-compose app container (#33) @hardillb 237 | 238 | #### 0.9.0: Release 239 | 240 | 241 | #### 0.8.0: Release 242 | 243 | - Fix broker comms http auth path (#27) @hardillb 244 | - add dep @flowforge/nr-project-nodes (#26) @Steve-Mcl 245 | - First pass of adding broker (#24) @hardillb 246 | - Add theme to NR container (#22) @hardillb 247 | 248 | #### 0.7.0: Release 249 | 250 | - Fix Python for sqlite (#20) @hardillb 251 | 252 | #### 0.6.0: Release 253 | 254 | 255 | #### 0.5.0: Release 256 | 257 | - Fix virtual host (#14) @hardillb 258 | 259 | #### 0.4.0: Release 260 | 261 | - Update project automation (#12) @knolleary 262 | - Fix node red path (#11) @hardillb 263 | - Remove local build script (#9) @hardillb 264 | - Add dev mode compose file (#10) @knolleary 265 | 266 | #### 0.3.0: Release 267 | 268 | - Add optional npm registy (#7) @hardillb 269 | 270 | #### 0.2.0: Release 271 | 272 | - Add default host config value of 0.0.0.0 (#6) @hardillb 273 | - Update README.md (#4) @hardillb 274 | - Add project workflow automation (#3) @knolleary 275 | - Dns readme (#2) @hardillb 276 | 277 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright FlowForge Inc, and other contributors, https://flowforge.com/ 2 | 3 | Apache License 4 | Version 2.0, January 2004 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | and distribution as defined by Sections 1 through 9 of this document. 13 | 14 | "Licensor" shall mean the copyright owner or entity authorized by 15 | the copyright owner that is granting the License. 16 | 17 | "Legal Entity" shall mean the union of the acting entity and all 18 | other entities that control, are controlled by, or are under common 19 | control with that entity. For the purposes of this definition, 20 | "control" means (i) the power, direct or indirect, to cause the 21 | direction or management of such entity, whether by contract or 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 23 | outstanding shares, or (iii) beneficial ownership of such entity. 24 | 25 | "You" (or "Your") shall mean an individual or Legal Entity 26 | exercising permissions granted by this License. 27 | 28 | "Source" form shall mean the preferred form for making modifications, 29 | including but not limited to software source code, documentation 30 | source, and configuration files. 31 | 32 | "Object" form shall mean any form resulting from mechanical 33 | transformation or translation of a Source form, including but 34 | not limited to compiled object code, generated documentation, 35 | and conversions to other media types. 36 | 37 | "Work" shall mean the work of authorship, whether in Source or 38 | Object form, made available under the License, as indicated by a 39 | copyright notice that is included in or attached to the work 40 | (an example is provided in the Appendix below). 41 | 42 | "Derivative Works" shall mean any work, whether in Source or Object 43 | form, that is based on (or derived from) the Work and for which the 44 | editorial revisions, annotations, elaborations, or other modifications 45 | represent, as a whole, an original work of authorship. For the purposes 46 | of this License, Derivative Works shall not include works that remain 47 | separable from, or merely link (or bind by name) to the interfaces of, 48 | the Work and Derivative Works thereof. 49 | 50 | "Contribution" shall mean any work of authorship, including 51 | the original version of the Work and any modifications or additions 52 | to that Work or Derivative Works thereof, that is intentionally 53 | submitted to Licensor for inclusion in the Work by the copyright owner 54 | or by an individual or Legal Entity authorized to submit on behalf of 55 | the copyright owner. For the purposes of this definition, "submitted" 56 | means any form of electronic, verbal, or written communication sent 57 | to the Licensor or its representatives, including but not limited to 58 | communication on electronic mailing lists, source code control systems, 59 | and issue tracking systems that are managed by, or on behalf of, the 60 | Licensor for the purpose of discussing and improving the Work, but 61 | excluding communication that is conspicuously marked or otherwise 62 | designated in writing by the copyright owner as "Not a Contribution." 63 | 64 | "Contributor" shall mean Licensor and any individual or Legal Entity 65 | on behalf of whom a Contribution has been received by Licensor and 66 | subsequently incorporated within the Work. 67 | 68 | 2. Grant of Copyright License. Subject to the terms and conditions of 69 | this License, each Contributor hereby grants to You a perpetual, 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 71 | copyright license to reproduce, prepare Derivative Works of, 72 | publicly display, publicly perform, sublicense, and distribute the 73 | Work and such Derivative Works in Source or Object form. 74 | 75 | 3. Grant of Patent License. Subject to the terms and conditions of 76 | this License, each Contributor hereby grants to You a perpetual, 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 78 | (except as stated in this section) patent license to make, have made, 79 | use, offer to sell, sell, import, and otherwise transfer the Work, 80 | where such license applies only to those patent claims licensable 81 | by such Contributor that are necessarily infringed by their 82 | Contribution(s) alone or by combination of their Contribution(s) 83 | with the Work to which such Contribution(s) was submitted. If You 84 | institute patent litigation against any entity (including a 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work 86 | or a Contribution incorporated within the Work constitutes direct 87 | or contributory patent infringement, then any patent licenses 88 | granted to You under this License for that Work shall terminate 89 | as of the date such litigation is filed. 90 | 91 | 4. Redistribution. You may reproduce and distribute copies of the 92 | Work or Derivative Works thereof in any medium, with or without 93 | modifications, and in Source or Object form, provided that You 94 | meet the following conditions: 95 | 96 | (a) You must give any other recipients of the Work or 97 | Derivative Works a copy of this License; and 98 | 99 | (b) You must cause any modified files to carry prominent notices 100 | stating that You changed the files; and 101 | 102 | (c) You must retain, in the Source form of any Derivative Works 103 | that You distribute, all copyright, patent, trademark, and 104 | attribution notices from the Source form of the Work, 105 | excluding those notices that do not pertain to any part of 106 | the Derivative Works; and 107 | 108 | (d) If the Work includes a "NOTICE" text file as part of its 109 | distribution, then any Derivative Works that You distribute must 110 | include a readable copy of the attribution notices contained 111 | within such NOTICE file, excluding those notices that do not 112 | pertain to any part of the Derivative Works, in at least one 113 | of the following places: within a NOTICE text file distributed 114 | as part of the Derivative Works; within the Source form or 115 | documentation, if provided along with the Derivative Works; or, 116 | within a display generated by the Derivative Works, if and 117 | wherever such third-party notices normally appear. The contents 118 | of the NOTICE file are for informational purposes only and 119 | do not modify the License. You may add Your own attribution 120 | notices within Derivative Works that You distribute, alongside 121 | or as an addendum to the NOTICE text from the Work, provided 122 | that such additional attribution notices cannot be construed 123 | as modifying the License. 124 | 125 | You may add Your own copyright statement to Your modifications and 126 | may provide additional or different license terms and conditions 127 | for use, reproduction, or distribution of Your modifications, or 128 | for any such Derivative Works as a whole, provided Your use, 129 | reproduction, and distribution of the Work otherwise complies with 130 | the conditions stated in this License. 131 | 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, 133 | any Contribution intentionally submitted for inclusion in the Work 134 | by You to the Licensor shall be under the terms and conditions of 135 | this License, without any additional terms or conditions. 136 | Notwithstanding the above, nothing herein shall supersede or modify 137 | the terms of any separate license agreement you may have executed 138 | with Licensor regarding such Contributions. 139 | 140 | 6. Trademarks. This License does not grant permission to use the trade 141 | names, trademarks, service marks, or product names of the Licensor, 142 | except as required for reasonable and customary use in describing the 143 | origin of the Work and reproducing the content of the NOTICE file. 144 | 145 | 7. Disclaimer of Warranty. Unless required by applicable law or 146 | agreed to in writing, Licensor provides the Work (and each 147 | Contributor provides its Contributions) on an "AS IS" BASIS, 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 149 | implied, including, without limitation, any warranties or conditions 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 151 | PARTICULAR PURPOSE. You are solely responsible for determining the 152 | appropriateness of using or redistributing the Work and assume any 153 | risks associated with Your exercise of permissions under this License. 154 | 155 | 8. Limitation of Liability. In no event and under no legal theory, 156 | whether in tort (including negligence), contract, or otherwise, 157 | unless required by applicable law (such as deliberate and grossly 158 | negligent acts) or agreed to in writing, shall any Contributor be 159 | liable to You for damages, including any direct, indirect, special, 160 | incidental, or consequential damages of any character arising as a 161 | result of this License or out of the use or inability to use the 162 | Work (including but not limited to damages for loss of goodwill, 163 | work stoppage, computer failure or malfunction, or any and all 164 | other commercial damages or losses), even if such Contributor 165 | has been advised of the possibility of such damages. 166 | 167 | 9. Accepting Warranty or Additional Liability. While redistributing 168 | the Work or Derivative Works thereof, You may choose to offer, 169 | and charge a fee for, acceptance of support, warranty, indemnity, 170 | or other liability obligations and/or rights consistent with this 171 | License. However, in accepting such obligations, You may act only 172 | on Your own behalf and on Your sole responsibility, not on behalf 173 | of any other Contributor, and only if You agree to indemnify, 174 | defend, and hold each Contributor harmless for any liability 175 | incurred by, or claims asserted against, such Contributor by reason 176 | of your accepting any such warranty or additional liability. 177 | 178 | END OF TERMS AND CONDITIONS 179 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlowFuse Docker Compose 2 | 3 | An example Docker Compose project to run FlowFuse 4 | 5 | ## Prerequisites 6 | 7 | ### Docker Compose 8 | 9 | FlowFuse uses Docker Compose to install and manager the required components. Instructions on how to install Docker Compose on your system can be found here: 10 | 11 | [https://docs.docker.com/compose/install/](https://docs.docker.com/compose/install/) 12 | 13 | FlowFuse requires at least Docker Compose v2 14 | 15 | These instructions assume you are running Docker on a Linux or MacOS host system. 16 | 17 | 18 | ### DNS 19 | 20 | To access the Projects created you will need to set up a wildcard DNS entry that points to the `domain` entered in the `etc/flowforge.yml` file. 21 | 22 | e.g. assuming that Docker is running on a machine with IP address `192.168.0.8` then an A record point to `*.example.com`. 23 | 24 | This will mean that any host at `example.com` will resolve to the `192.168.0.8`. 25 | 26 | **Note** When testing locally you can not use the loopback address `127.0.0.1` for this, e.g. in the `/etc/hosts` file, as this will resolve to the TCP/IP stack inside each container. 27 | 28 | ## Installing FlowFuse 29 | 30 | ### Building Containers 31 | 32 | To build the required containers simply run `./build-containers.sh`. 33 | 34 | This will build and tag `flowfuse/forge-docker` and `flowfuse/node-red` and `flowfuse/file-server`. 35 | 36 | #### flowfuse/flowforge-docker 37 | 38 | This container holds the FlowFuse App and the Docker Driver. 39 | 40 | #### flowfuse/node-red 41 | 42 | This is a basic Node-RED image with the FlowFuse Launcher and the required Node-RED plugins to talk to the FlowFuse Platform. 43 | 44 | This is the container you can customize for your deployment. 45 | 46 | #### flowfuse/file-server 47 | 48 | This holds the Object Store used to allow persistent file storage for Projects running on Docker 49 | 50 | ## Configuration 51 | 52 | Configuration details are stored in the `etc/flowforge.yml` file which is mapped into the `flowforge/forge-docker` container. You will need to edit this file to update the `domain` and `base_url` entries to match the DNS settings. 53 | 54 | You also need to update the `VIRTUAL_HOST` entry in the `docker-compose.yml` file to use the same domain as in the `etc/flowforge.yml` file. 55 | 56 | You should also update the `email` section to point to a local SMTP server so you can invite users to join teams. 57 | 58 | ### Creating Instance 59 | 60 | Once the container have been built you can start the FlowFuse by running: 61 | 62 | ``` 63 | docker-compose -p flowforge up -d 64 | ``` 65 | 66 | This will also create a directory called `db` to hold the database files used to store project instance and user information. 67 | 68 | # Upgrading 69 | 70 | If upgrading from version before version 1.2.0 you will need to manually create the database for the persistent context store. 71 | 72 | To do this you will need to run the following command after starting: 73 | 74 | ``` 75 | docker exec -it flowforge_postgres_1 /docker-entrypoint-initdb.d/setup-context-db.sh 76 | ``` 77 | 78 | # Development Mode 79 | 80 | **This is experimental** 81 | 82 | If you are actively developing FlowFuse, the following instructions can be used 83 | to run it with the Docker driver using a locally mounted source tree. 84 | 85 | 1. Ensure that you have all of the FlowFuse source repositories checked out next to each 86 | other - including this repository. 87 | 88 | 2. Run `npm install` in each repository that has a package.json file. 89 | 90 | 3. In the `flowforge` repo, run `npm run dev:local` to setup proper dev symlinks 91 | between the repos. 92 | 93 | 4. Follow the instructions above to setup DNS. 94 | 95 | 5. Edit the `etc/flowforge.yml` file in the `flowforge` repository to use the docker driver: 96 | ``` 97 | port: 3000 98 | host: 0.0.0.0 99 | domain: example.com 100 | base_url: http://forge.example.com 101 | api_url: http://forge:3000 102 | 103 | driver: 104 | type: docker 105 | options: 106 | socket: /tmp/docker.sock 107 | ``` 108 | 109 | 110 | 6. Depending on what OS you are running on, the core project has one binary 111 | dependency that needs to be rebuilt for it to work inside Docker - `bcrypt`. 112 | The super hacky way to get that to work is to edit `flowforge/package.json` and 113 | modify the `serve` task to first reinstall that module: 114 | ``` 115 | "serve": "npm uninstall bcrypt && npm install bcrypt && npm-run-all --parallel build-watch start-watch" 116 | ``` 117 | You only need to do this the first time you run under docker - you can then revert 118 | that change for the subsequent runs. 119 | 120 | **Note:** You will need to reinstall the module when you go back to running outside 121 | of docker. 122 | 123 | 7. Start the platform with: `docker-compose -f docker-compose-local-dev.yml up --build` 124 | 125 | That will start the standard environment, but the `forge` container will have the 126 | local source tree mounted, and use `npm run serve` to start the code. This means 127 | it will automatically rebuild/restart whenever source code changes are made. 128 | 129 | 130 | -------------------------------------------------------------------------------- /UPGRADE.md: -------------------------------------------------------------------------------- 1 | # FlowFuse Docker Compose migration guide 2 | 3 | This guide will help you migrate your existing FlowFuse platform, created using version prior to `2.10.0`, 4 | to the new Docker Compose approach available since `2.11.0`. 5 | 6 | 7 | ## Changes 8 | 9 | Version `2.10.0` introduced a new way to handle FlowFuse platform configurations. The new approach uses Compose `configs` to manage the configuration files and 10 | `volumes` to manage data persistence. Additionaly, configuration can be adjusted using environmental variables with `.env` file. 11 | This allows for easier management of the platform and better separation of concerns. 12 | 13 | 14 | ## Prerequisites 15 | 16 | - access to existing installation files (run `docker compose ls` to list project and path to it's configuration files) 17 | - access to configuration files: 18 | - `./etc/flowforge.yml` - FlowFuse configuration file 19 | - `./db` - directory with database files 20 | - `./etc/flowforge-storage.yml` - storage configuration file 21 | - `./certs` - directory with custom certificates 22 | 23 | ## Migration steps 24 | 25 | 1. **Backup existing configuration files** 26 | 27 | Before starting the migration process, make sure to backup the existing configuration files. This will allow you to revert to the previous state in case of any issues. 28 | 29 | ```bash 30 | cp ./etc/flowforge.yml ./etc/flowforge.yml.bak 31 | cp ./etc/flowforge-storage.yml ./etc/flowforge-storage.yml.bak 32 | cp -r ./db ./db.bak 33 | ``` 34 | 35 | 2. **Download new files** 36 | 37 | Download the new Docker Compose file and `.env` file from the FlowFuse repository. 38 | 39 | ```bash 40 | curl -o docker-compose-new.yml https://raw.githubusercontent.com/flowfuse/docker-compose/main/docker-compose.yml 41 | curl -o .env https://raw.githubusercontent.com/flowfuse/docker-compose/main/.env.example 42 | ``` 43 | 44 | 3. **Move configurations to the new approach** 45 | 46 | * Copy content of `./etc/flowforge.yml` file to `docker-compose-new.yml` file, to `configs.flowfuse.content` section. Remove all commented lines. Maintain indentation. 47 | * Make sure, that `broker.url` is seto fo `mqtt://broker:1883`. Update if needed. 48 | * Copy content of `./etc/flowforge-storage.yml` file to `docker-compose-new.yml` file, to `configs.flowfuse_storage.content` section. Remove all commented lines. Maintain indentation. 49 | * Set the `DOMAIN` variable in the `.env` file to the domain used by your instance of FlowFuse platform. 50 | * If FlowFuse application is running outside of the `DOMAIN` scope, set it as a value of `APPLICATION_DOMAIN` variable in the `.env` file. 51 | * If application should be accessible via seured connection (HTTPS), set `TLS_ENABLED` variable to `true` in `.env` file. 52 | * If custom certificates are used, copy their content to `.env` file, to `TLS_CERTIFICATE` and `TLS_KEY` variables. They should look like this: 53 | 54 | ```bash 55 | TLS_CERTIFICATE=" 56 | -----BEGIN CERTIFICATE----- 57 | MIIFfzCCBKegAwIBAgISA0 58 | ... 59 | -----END CERTIFICATE----- 60 | -----BEGIN CERTIFICATE----- 61 | MIIFfzCCBKegAwIBAgISA0 62 | ... 63 | -----END CERTIFICATE----- 64 | " 65 | TLS_KEY=" 66 | -----BEGIN PRIVATE KEY----- 67 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD 68 | ... 69 | -----END PRIVATE KEY----- 70 | " 71 | ``` 72 | 73 | * If custom certificates are used and FlowFuse application is running on a different domain than other stack components (defined in `APPLICATION_DOMAIN` variable), 74 | use `APP_TLS_CERTIFICATE` and `APP_TLS_KEY` variabls to provide certificate and it's key. They should look like this: 75 | 76 | ```bash 77 | APP_TLS_CERTIFICATE=" 78 | -----BEGIN CERTIFICATE----- 79 | MIIFfzCCBKegAwIBAgISA0 80 | ... 81 | -----END CERTIFICATE----- 82 | -----BEGIN CERTIFICATE----- 83 | MIIFfzCCBKegAwIBAgISA0 84 | ... 85 | -----END CERTIFICATE----- 86 | " 87 | APP_TLS_KEY=" 88 | -----BEGIN PRIVATE KEY----- 89 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD 90 | ... 91 | -----END PRIVATE KEY----- 92 | " 93 | ``` 94 | 95 | 4. **Migrate database files** 96 | 97 | Move the database files from host to the new volume. This will allow you to keep the existing data. 98 | 99 | * Create volume for database files: 100 | 101 | ```bash 102 | docker volume create flowfuse_db 103 | ``` 104 | * Find currently running FlowFuse project and stop it: 105 | 106 | ```bash 107 | docker compose ls 108 | docker compose -p $project down 109 | ``` 110 | 111 | * Copy the database files to the new volume: 112 | 113 | ```bash 114 | docker run --rm -v flowfuse_db:/data -v $(pwd)/db:/backup alpine sh -c "cp -a /backup/. /data/" 115 | ``` 116 | 117 | 5. **Rename files** 118 | 119 | In order to maintain the same file structurem, rename the compose files. 120 | 121 | ```bash 122 | mv docker-compose.yml docker-compose-old.yml 123 | mv docker-compose-new.yml docker-compose.yml 124 | ``` 125 | 126 | 6. **Start FlowFuse** 127 | 128 | Start the new FlowFuse platform using the new Docker Compose file. 129 | 130 | * With automatic TLS certificate generation: 131 | ```bash 132 | docker compose -f docker-compose.yml --profile autotls -p flowfuse up -d 133 | ``` 134 | 135 | * In all other cases 136 | 137 | ```bash 138 | docker compose -p flowfuse up -d 139 | ``` 140 | 141 | 7. **Verify the migration** 142 | 143 | Verify that the new FlowFuse platform is working correctly and it is accessible using the domain set in the `.env` file. 144 | Login credentials should remain the same as before the migration, as well as platform configuration. 145 | Restart the Node-RED instances if they appear in `Starting` state. 146 | 147 | 8 **Cleanup** 148 | 149 | After verifying that the new FlowFuse platform is working correctly, you can remove the old configuration files. 150 | 151 | ```bash 152 | rm ./etc/flowforge.yml ./etc/flowforge.yml.bak 153 | rm ./etc/flowforge-storage.yml.bak ./etc/flowforge-storage.yml 154 | rm -rf ./db ./db.bak 155 | rm -f ./docker-compose-old.yml 156 | ``` 157 | 158 | -------------------------------------------------------------------------------- /broker/mosquitto.conf: -------------------------------------------------------------------------------- 1 | per_listener_settings false 2 | allow_anonymous false 3 | 4 | listener 1883 0.0.0.0 5 | 6 | listener 1884 0.0.0.0 7 | protocol websockets 8 | 9 | auth_plugin /mosquitto/go-auth.so 10 | auth_opt_backends http 11 | auth_opt_hasher bcrypt 12 | auth_opt_cache true 13 | auth_opt_auth_cache_seconds 30 14 | auth_opt_acl_cache_seconds 90 15 | auth_opt_auth_jitter_second 3 16 | auth_opt_acl_jitter_seconds 5 17 | 18 | auth_opt_http_host forge 19 | auth_opt_http_port 3000 20 | auth_opt_http_getuser_uri /api/comms/auth/client 21 | auth_opt_http_aclcheck_uri /api/comms/auth/acl 22 | 23 | -------------------------------------------------------------------------------- /build-containers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker build flowforge-docker -t flowfuse/forge-docker 4 | docker build node-red-container -t flowfuse/node-red 5 | docker build file-server -t flowfuse/file-server 6 | -------------------------------------------------------------------------------- /docker-compose-local-dev.yml: -------------------------------------------------------------------------------- 1 | version: "3.3" 2 | services: 3 | nginx: 4 | image: jwilder/nginx-proxy 5 | networks: 6 | - flowforge 7 | restart: always 8 | volumes: 9 | - "/var/run/docker.sock:/tmp/docker.sock:ro" 10 | ports: 11 | - "80:80" 12 | # - "443:443" 13 | postgres: 14 | image: postgres 15 | networks: 16 | - flowforge 17 | restart: always 18 | environment: 19 | POSTGRES_PASSWORD: secret 20 | POSTGRES_USER: root 21 | volumes: 22 | - "./db:/var/lib/postgresql/data" 23 | - "./setup-db.sh:/docker-entrypoint-initdb.d/setup-db.sh" 24 | 25 | flowforge-broker: 26 | image: "iegomez/mosquitto-go-auth" 27 | networks: 28 | - flowforge 29 | restart: always 30 | ulimits: 31 | nofile: 2048 32 | environment: 33 | - "VIRTUAL_HOST=mqtt.example.com" 34 | - "VIRTUAL_PORT=1884" 35 | - "LETSENCRYPT_HOST=mqtt.example.com" 36 | volumes: 37 | - "./broker/mosquitto.conf:/etc/mosquitto/mosquitto.conf" 38 | forge: 39 | build: 40 | context: "./flowforge-docker-local-dev/" 41 | networks: 42 | - flowforge 43 | restart: always 44 | environment: 45 | - "VIRTUAL_HOST=forge.example.com" 46 | volumes: 47 | - "/var/run/docker.sock:/tmp/docker.sock" 48 | - "../:/data" 49 | depends_on: 50 | - "postgres" 51 | - "nginx" 52 | file-server: 53 | build: 54 | context: "./file-server" 55 | image: "flowforge/file-server" 56 | networks: 57 | - flowforge 58 | restart: always 59 | volumes: 60 | - "./etc/flowforge-storage.yml:/usr/src/flowforge-file-server/etc/flowforge-storage.yml" 61 | - "./fileStorage:/usr/src/flowforge-file-server/var/root" 62 | depends_on: 63 | - "forge" 64 | 65 | networks: 66 | flowforge: -------------------------------------------------------------------------------- /docker-compose-quick-start.yml: -------------------------------------------------------------------------------- 1 | name: flowfuse 2 | 3 | configs: 4 | flowfuse: 5 | content: | 6 | port: 3000 7 | host: 0.0.0.0 8 | domain: ${DOMAIN:?error} 9 | base_url: http://forge.${DOMAIN:?error} 10 | api_url: http://forge:3000 11 | db: 12 | logging: false 13 | type: postgres 14 | host: postgres 15 | database: flowfuse 16 | user: forge 17 | password: secret 18 | driver: 19 | type: docker 20 | options: 21 | socket: /tmp/docker.sock 22 | storage: 23 | enabled: true 24 | broker: 25 | url: mqtt://broker:1883 26 | public_url: ws://mqtt.${DOMAIN:?error} 27 | fileStore: 28 | url: http://file-server:3001 29 | flowfuse_broker: 30 | content: | 31 | per_listener_settings false 32 | allow_anonymous false 33 | listener 1883 0.0.0.0 34 | listener 1884 0.0.0.0 35 | protocol websockets 36 | auth_plugin /mosquitto/go-auth.so 37 | auth_opt_backends http 38 | auth_opt_hasher bcrypt 39 | auth_opt_cache true 40 | auth_opt_auth_cache_seconds 30 41 | auth_opt_acl_cache_seconds 90 42 | auth_opt_auth_jitter_second 3 43 | auth_opt_acl_jitter_seconds 5 44 | auth_opt_http_host forge 45 | auth_opt_http_port 3000 46 | auth_opt_http_getuser_uri /api/comms/auth/client 47 | auth_opt_http_aclcheck_uri /api/comms/auth/acl 48 | flowfuse_storage: 49 | content: | 50 | port: 3001 51 | host: 0.0.0.0 52 | base_url: http://forge:3000 53 | driver: 54 | type: localfs 55 | quota: 104857600 56 | options: 57 | root: var/root 58 | context: 59 | type: sequelize 60 | quota: 1048576 61 | options: 62 | type: postgres 63 | host: postgres 64 | database: ff-context 65 | username: forge 66 | password: secret 67 | nginx: 68 | content: | 69 | client_max_body_size 5m; 70 | postgres_db_setup: 71 | content: | 72 | #!/bin/sh 73 | set -e 74 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 75 | CREATE USER forge WITH PASSWORD 'secret'; 76 | CREATE DATABASE flowfuse; 77 | GRANT ALL PRIVILEGES ON DATABASE flowfuse TO forge; 78 | ESQL 79 | postgres_context_setup: 80 | content: | 81 | #!/bin/sh 82 | set -e 83 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 84 | SELECT 'CREATE DATABASE "ff-context"' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'ff-context')\gexec 85 | GRANT ALL PRIVILEGES ON DATABASE "ff-context" TO "forge"; 86 | ESQL 87 | 88 | services: 89 | nginx: 90 | image: nginxproxy/nginx-proxy:1.6.0 91 | networks: 92 | - flowforge 93 | restart: always 94 | volumes: 95 | - "/var/run/docker.sock:/tmp/docker.sock:ro" 96 | configs: 97 | - source: nginx 98 | target: /etc/nginx/conf.d/my_proxy.conf 99 | ports: 100 | - "80:80" 101 | healthcheck: 102 | test: "curl -s -I http://localhost | head -n 1 | grep -q 503" 103 | interval: 5s 104 | timeout: 5s 105 | retries: 3 106 | start_period: 10s 107 | 108 | postgres: 109 | image: postgres:14 110 | networks: 111 | - flowforge 112 | restart: always 113 | environment: 114 | POSTGRES_PASSWORD: secret 115 | POSTGRES_USER: root 116 | configs: 117 | - source: postgres_db_setup 118 | target: /docker-entrypoint-initdb.d/01-setup-db.sh 119 | mode: 0664 120 | - source: postgres_context_setup 121 | target: /docker-entrypoint-initdb.d/02-setup-context-db.sh 122 | mode: 0664 123 | volumes: 124 | - db:/var/lib/postgresql/data 125 | healthcheck: 126 | test: ["CMD-SHELL", "pg_isready -U root"] 127 | interval: 10s 128 | timeout: 5s 129 | retries: 3 130 | start_period: 10s 131 | 132 | broker: 133 | image: "iegomez/mosquitto-go-auth" 134 | networks: 135 | - flowforge 136 | restart: always 137 | ulimits: 138 | nofile: 2048 139 | environment: 140 | - "VIRTUAL_HOST=mqtt.${DOMAIN:?error}" 141 | - "VIRTUAL_PORT=1884" 142 | - "LETSENCRYPT_HOST=mqtt.${DOMAIN:?error}" 143 | configs: 144 | - source: flowfuse_broker 145 | target: /etc/mosquitto/mosquitto.conf 146 | mode: 0664 147 | 148 | forge: 149 | image: "flowfuse/forge-docker" 150 | networks: 151 | - flowforge 152 | restart: always 153 | environment: 154 | - "VIRTUAL_HOST=forge.${DOMAIN:?error}" 155 | - "LETSENCRYPT_HOST=forge.${DOMAIN:?error}" 156 | configs: 157 | - source: flowfuse 158 | target: /usr/src/forge/etc/flowforge.yml 159 | volumes: 160 | - "/var/run/docker.sock:/tmp/docker.sock" 161 | - flowfuse-persistent-storage:/opt/persistent-storage 162 | depends_on: 163 | - "postgres" 164 | - "nginx" 165 | - "broker" 166 | healthcheck: 167 | test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"] 168 | interval: 5s 169 | timeout: 5s 170 | retries: 3 171 | start_period: 10s 172 | 173 | file-server: 174 | image: "flowfuse/file-server" 175 | networks: 176 | - flowforge 177 | restart: always 178 | configs: 179 | - source: flowfuse_storage 180 | target: /usr/src/flowforge-file-server/etc/flowforge-storage.yml 181 | volumes: 182 | - fileStorage:/usr/src/flowforge-file-server/var/root 183 | depends_on: 184 | - "forge" 185 | healthcheck: 186 | test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3001/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"] 187 | interval: 5s 188 | timeout: 5s 189 | retries: 3 190 | start_period: 10s 191 | 192 | networks: 193 | flowforge: 194 | 195 | volumes: 196 | flowfuse-persistent-storage: 197 | db: 198 | fileStorage: 199 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | name: flowfuse 2 | 3 | configs: 4 | flowfuse: 5 | content: | 6 | port: 3000 7 | host: 0.0.0.0 8 | domain: ${DOMAIN:?error} 9 | base_url: http${TLS_ENABLED:+s}://${APPLICATION_DOMAIN:-forge.${DOMAIN}} 10 | api_url: http://forge:3000 11 | create_admin: ${CREATE_ADMIN:-false} 12 | db: 13 | logging: false 14 | type: postgres 15 | host: ${DB_HOST:-postgres} 16 | user: ${DB_USER:-forge} 17 | password: ${DB_PASSWORD:-secret} 18 | email: 19 | enabled: ${EMAIL_ENABLED:-false} 20 | from: '"FlowFuse" ' 21 | smtp: 22 | host: ${EMAIL_HOST} 23 | port: ${EMAIL_PORT:-587} 24 | secure: ${EMAIL_SECURE:-false} 25 | auth: 26 | user: ${EMAIL_USER} 27 | pass: ${EMAIL_PASSWORD} 28 | driver: 29 | type: docker 30 | options: 31 | socket: /tmp/docker.sock 32 | ${DOCKER_DRIVER_PRIVATE_CA_PATH:+privateCA: ${DOCKER_DRIVER_PRIVATE_CA_PATH}} 33 | storage: 34 | enabled: true 35 | broker: 36 | url: mqtt://broker:1883 37 | public_url: ws${TLS_ENABLED:+s}://mqtt.${DOMAIN:?error} 38 | teamBroker: 39 | enabled: true 40 | fileStore: 41 | enable: true 42 | url: http://file-server:3001 43 | flowfuse_storage: 44 | content: | 45 | port: 3001 46 | host: 0.0.0.0 47 | base_url: http://forge:3000 48 | driver: 49 | type: localfs 50 | quota: 104857600 51 | options: 52 | root: var/root 53 | context: 54 | type: sequelize 55 | quota: 1048576 56 | options: 57 | type: postgres 58 | host: ${DB_HOST:-postgres} 59 | database: ff-context 60 | username: ${DB_USER:-forge} 61 | password: ${DB_PASSWORD:-secret} 62 | nginx: 63 | content: | 64 | client_max_body_size 5m; 65 | nginx_main_tls_crt: 66 | environment: TLS_CERTIFICATE 67 | nginx_main_tls_key: 68 | environment: TLS_KEY 69 | nginx_app_tls_crt: 70 | environment: APP_TLS_CERTIFICATE 71 | nginx_app_tls_key: 72 | environment: APP_TLS_KEY 73 | nginx_stream: 74 | content: | 75 | # stream { 76 | # server { 77 | # listen 1884 ssl; 78 | # ssl_protocols TLSv1.2; 79 | # ssl_certificate /etc/nginx/certs/${DOMAIN}.crt; 80 | # ssl_certificate_key /etc/nginx/certs/${DOMAIN}.key; 81 | # proxy_pass broker:1883; 82 | # } 83 | # } 84 | postgres_db_setup: 85 | content: | 86 | #!/bin/sh 87 | set -e 88 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 89 | CREATE USER ${DB_USER:-forge} WITH PASSWORD '${DB_PASSWORD:-secret}'; 90 | CREATE DATABASE flowforge; 91 | GRANT ALL PRIVILEGES ON DATABASE flowforge TO ${DB_USER:-forge}; 92 | ESQL 93 | postgres_context_setup: 94 | content: | 95 | #!/bin/sh 96 | set -e 97 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 98 | SELECT 'CREATE DATABASE "ff-context"' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'ff-context')\gexec 99 | GRANT ALL PRIVILEGES ON DATABASE "ff-context" TO "${DB_USER:-forge}"; 100 | ESQL 101 | emqx: 102 | content: | 103 | authentication = [ 104 | { 105 | backend = http 106 | body { 107 | clientId = "$${clientid}" 108 | password = "$${password}" 109 | username = "$${username}" 110 | } 111 | connect_timeout = "15s" 112 | enable = true 113 | enable_pipelining = 1 114 | headers { 115 | content-type = "application/json" 116 | } 117 | mechanism = password_based 118 | method = post 119 | pool_size = 8 120 | request_timeout = "15s" 121 | ssl { 122 | ciphers = [] 123 | depth = 10 124 | enable = false 125 | hibernate_after = "5s" 126 | log_level = notice 127 | reuse_sessions = true 128 | secure_renegotiate = true 129 | verify = verify_peer 130 | versions = [ 131 | "tlsv1.3", 132 | "tlsv1.2" 133 | ] 134 | } 135 | url = "http://forge:3000/api/comms/v2/auth" 136 | }, 137 | { 138 | backend = built_in_database 139 | bootstrap_file = "$${EMQX_ETC_DIR}/auth-built-in-db-bootstrap.csv" 140 | bootstrap_type = plain 141 | enable = true 142 | mechanism = password_based 143 | password_hash_algorithm {name = plain, salt_position = disable} 144 | user_id_type = username 145 | } 146 | ] 147 | authorization { 148 | cache { 149 | enable = true 150 | excludes = [] 151 | max_size = 32 152 | ttl = "1m" 153 | } 154 | deny_action = ignore 155 | no_match = allow 156 | sources = [ 157 | { 158 | body { 159 | action = "$${action}" 160 | topic = "$${topic}" 161 | username = "$${username}" 162 | } 163 | connect_timeout = "15s" 164 | enable = true 165 | enable_pipelining = 1 166 | headers { 167 | content-type = "application/json" 168 | } 169 | method = post 170 | pool_size = 8 171 | request_timeout = "30s" 172 | ssl { 173 | ciphers = [] 174 | depth = 10 175 | enable = false 176 | hibernate_after = "5s" 177 | log_level = notice 178 | reuse_sessions = true 179 | secure_renegotiate = true 180 | verify = verify_peer 181 | versions = [ 182 | "tlsv1.3", 183 | "tlsv1.2" 184 | ] 185 | } 186 | type = http 187 | url = "http://forge:3000/api/comms/v2/acls" 188 | } 189 | ] 190 | } 191 | mqtt { 192 | max_packet_size: 128MB 193 | } 194 | listeners { 195 | ssl { 196 | default { 197 | acceptors = 16 198 | access_rules = [ 199 | "allow all" 200 | ] 201 | bind = "0.0.0.0:8883" 202 | enable = false 203 | enable_authn = true 204 | max_conn_rate = infinity 205 | max_connections = infinity 206 | mountpoint = "$${client_attrs.team}" 207 | proxy_protocol = false 208 | proxy_protocol_timeout = "3s" 209 | ssl_options { 210 | cacertfile = "$${EMQX_ETC_DIR}/certs/cacert.pem" 211 | certfile = "$${EMQX_ETC_DIR}/certs/cert.pem" 212 | ciphers = [] 213 | client_renegotiation = true 214 | depth = 10 215 | enable_crl_check = false 216 | fail_if_no_peer_cert = false 217 | gc_after_handshake = false 218 | handshake_timeout = "15s" 219 | hibernate_after = "5s" 220 | honor_cipher_order = true 221 | keyfile = "$${EMQX_ETC_DIR}/certs/key.pem" 222 | log_level = notice 223 | ocsp { 224 | enable_ocsp_stapling = false 225 | refresh_http_timeout = "15s" 226 | refresh_interval = "5m" 227 | } 228 | reuse_sessions = true 229 | secure_renegotiate = true 230 | verify = verify_none 231 | versions = [ 232 | "tlsv1.3", 233 | "tlsv1.2" 234 | ] 235 | } 236 | tcp_options { 237 | active_n = 100 238 | backlog = 1024 239 | buffer = "4KB" 240 | high_watermark = "1MB" 241 | keepalive = none 242 | nodelay = true 243 | reuseaddr = true 244 | send_timeout = "15s" 245 | send_timeout_close = true 246 | } 247 | zone = default 248 | } 249 | } 250 | tcp { 251 | default { 252 | acceptors = 16 253 | access_rules = [ 254 | "allow all" 255 | ] 256 | bind = "0.0.0.0:1883" 257 | enable = true 258 | enable_authn = true 259 | max_conn_rate = infinity 260 | max_connections = infinity 261 | mountpoint = "$${client_attrs.team}" 262 | proxy_protocol = false 263 | proxy_protocol_timeout = "3s" 264 | tcp_options { 265 | active_n = 100 266 | backlog = 1024 267 | buffer = "4KB" 268 | high_watermark = "1MB" 269 | keepalive = none 270 | nodelay = true 271 | reuseaddr = true 272 | send_timeout = "15s" 273 | send_timeout_close = true 274 | } 275 | zone = default 276 | } 277 | } 278 | ws { 279 | default { 280 | acceptors = 16 281 | access_rules = [ 282 | "allow all" 283 | ] 284 | bind = "0.0.0.0:8083" 285 | enable = true 286 | enable_authn = true 287 | max_conn_rate = infinity 288 | max_connections = infinity 289 | mountpoint = "$${client_attrs.team}" 290 | proxy_protocol = false 291 | proxy_protocol_timeout = "3s" 292 | tcp_options { 293 | active_n = 100 294 | backlog = 1024 295 | buffer = "4KB" 296 | high_watermark = "1MB" 297 | keepalive = none 298 | nodelay = true 299 | reuseaddr = true 300 | send_timeout = "15s" 301 | send_timeout_close = true 302 | } 303 | websocket { 304 | allow_origin_absence = true 305 | check_origin_enable = false 306 | check_origins = "http://localhost:18083, http://127.0.0.1:18083" 307 | compress = false 308 | deflate_opts { 309 | client_context_takeover = takeover 310 | client_max_window_bits = 15 311 | mem_level = 8 312 | server_context_takeover = takeover 313 | server_max_window_bits = 15 314 | strategy = default 315 | } 316 | fail_if_no_subprotocol = true 317 | idle_timeout = "7200s" 318 | max_frame_size = infinity 319 | mqtt_path = "/" 320 | mqtt_piggyback = multiple 321 | proxy_address_header = "x-forwarded-for" 322 | proxy_port_header = "x-forwarded-port" 323 | supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" 324 | validate_utf8 = true 325 | } 326 | zone = default 327 | } 328 | } 329 | wss { 330 | default { 331 | acceptors = 16 332 | access_rules = [ 333 | "allow all" 334 | ] 335 | bind = "0.0.0.0:8084" 336 | enable = false 337 | enable_authn = true 338 | max_conn_rate = infinity 339 | max_connections = infinity 340 | mountpoint = "$${client_attrs.team}" 341 | proxy_protocol = false 342 | proxy_protocol_timeout = "3s" 343 | ssl_options { 344 | cacertfile = "$${EMQX_ETC_DIR}/certs/cacert.pem" 345 | certfile = "$${EMQX_ETC_DIR}/certs/cert.pem" 346 | ciphers = [] 347 | client_renegotiation = true 348 | depth = 10 349 | fail_if_no_peer_cert = false 350 | handshake_timeout = "15s" 351 | hibernate_after = "5s" 352 | honor_cipher_order = true 353 | keyfile = "$${EMQX_ETC_DIR}/certs/key.pem" 354 | log_level = notice 355 | reuse_sessions = true 356 | secure_renegotiate = true 357 | verify = verify_none 358 | versions = [ 359 | "tlsv1.3", 360 | "tlsv1.2" 361 | ] 362 | } 363 | tcp_options { 364 | active_n = 100 365 | backlog = 1024 366 | buffer = "4KB" 367 | high_watermark = "1MB" 368 | keepalive = none 369 | nodelay = true 370 | reuseaddr = true 371 | send_timeout = "15s" 372 | send_timeout_close = true 373 | } 374 | websocket { 375 | allow_origin_absence = true 376 | check_origin_enable = false 377 | check_origins = "http://localhost:18083, http://127.0.0.1:18083" 378 | compress = false 379 | deflate_opts { 380 | client_context_takeover = takeover 381 | client_max_window_bits = 15 382 | mem_level = 8 383 | server_context_takeover = takeover 384 | server_max_window_bits = 15 385 | strategy = default 386 | } 387 | fail_if_no_subprotocol = true 388 | idle_timeout = "7200s" 389 | max_frame_size = infinity 390 | mqtt_path = "/" 391 | mqtt_piggyback = multiple 392 | proxy_address_header = "x-forwarded-for" 393 | proxy_port_header = "x-forwarded-port" 394 | supported_subprotocols = "mqtt, mqtt-v3, mqtt-v3.1.1, mqtt-v5" 395 | validate_utf8 = true 396 | } 397 | zone = default 398 | } 399 | } 400 | } 401 | api_key { 402 | bootstrap_file = "/mounted/config/api-keys" 403 | } 404 | emqx-api: 405 | content: | 406 | flowfuse:verySecret:administrator 407 | 408 | services: 409 | nginx: 410 | image: nginxproxy/nginx-proxy:1.6.0 411 | networks: 412 | - flowforge 413 | restart: always 414 | volumes: 415 | - "/var/run/docker.sock:/tmp/docker.sock:ro" 416 | - "nginx-proxy-certs:/etc/nginx/certs" 417 | - "nginx-proxy-html:/usr/share/nginx/html" 418 | configs: 419 | - source: nginx 420 | target: /etc/nginx/conf.d/my_proxy.conf 421 | - source: nginx_main_tls_crt 422 | target: /etc/nginx/certs/${DOMAIN:?error}.crt 423 | - source: nginx_main_tls_key 424 | target: /etc/nginx/certs/${DOMAIN:?error}.key 425 | - source: nginx_app_tls_crt 426 | target: /etc/nginx/certs/${APPLICATION_DOMAIN:-forge.${DOMAIN}}.crt 427 | - source: nginx_app_tls_key 428 | target: /etc/nginx/certs/${APPLICATION_DOMAIN:-forge.${DOMAIN}}.key 429 | - source: nginx_stream 430 | target: /etc/nginx/toplevel.conf.d/mqtt.conf 431 | ports: 432 | - "80:80" 433 | - "443:443" 434 | - "1884:1884" 435 | environment: 436 | - HTTPS_METHOD=${TLS_ENABLED:+redirect} 437 | healthcheck: 438 | test: "curl -s -I http://localhost | head -n 1 | grep -q 503" 439 | interval: 5s 440 | timeout: 5s 441 | retries: 3 442 | start_period: 10s 443 | 444 | postgres: 445 | image: postgres:14 446 | networks: 447 | - flowforge 448 | restart: always 449 | environment: 450 | POSTGRES_PASSWORD: secret 451 | POSTGRES_USER: root 452 | configs: 453 | - source: postgres_db_setup 454 | target: /docker-entrypoint-initdb.d/01-setup-db.sh 455 | mode: 0664 456 | - source: postgres_context_setup 457 | target: /docker-entrypoint-initdb.d/02-setup-context-db.sh 458 | mode: 0664 459 | volumes: 460 | - db:/var/lib/postgresql/data 461 | healthcheck: 462 | test: ["CMD-SHELL", "pg_isready -U root"] 463 | interval: 10s 464 | timeout: 5s 465 | retries: 3 466 | start_period: 10s 467 | 468 | broker: 469 | image: emqx/emqx:5.8.0 470 | networks: 471 | - flowforge 472 | ports: 473 | - 1883:1883 474 | healthcheck: 475 | test: ["CMD", "/opt/emqx/bin/emqx", "ctl", "status"] 476 | interval: 5s 477 | timeout: 25s 478 | retries: 5 479 | environment: 480 | - "VIRTUAL_HOST=broker.${DOMAIN:?error},mqtt.${DOMAIN:?error}" 481 | - "VIRTUAL_PORT=8083" 482 | - "LETSENCRYPT_HOST=broker.${DOMAIN:?error},mqtt.${DOMAIN:?error}" 483 | - "EMQX_DASHBOARD__DEFAULT_PASSWORD=topSecret" 484 | configs: 485 | - source: emqx 486 | target: /opt/emqx/data/configs/cluster.hocon 487 | mode: 0664 488 | - source: emqx-api 489 | target: /mounted/config/api-keys 490 | mode: 0664 491 | volumes: 492 | - emqx:/opt/emqx/data 493 | 494 | forge: 495 | image: "flowfuse/forge-docker:2.18.0" 496 | networks: 497 | - flowforge 498 | restart: always 499 | environment: 500 | - "VIRTUAL_HOST=${APPLICATION_DOMAIN:-forge.${DOMAIN}}" 501 | - "LETSENCRYPT_HOST=${APPLICATION_DOMAIN:-forge.${DOMAIN}}" 502 | configs: 503 | - source: flowfuse 504 | target: /usr/src/forge/etc/flowforge.yml 505 | volumes: 506 | - "/var/run/docker.sock:/tmp/docker.sock" 507 | - flowfuse-persistent-storage:/opt/persistent-storage 508 | depends_on: 509 | - "postgres" 510 | - "nginx" 511 | - "broker" 512 | healthcheck: 513 | test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"] 514 | interval: 5s 515 | timeout: 5s 516 | retries: 3 517 | start_period: 10s 518 | 519 | file-server: 520 | image: "flowfuse/file-server:2.18.0" 521 | networks: 522 | - flowforge 523 | restart: always 524 | configs: 525 | - source: flowfuse_storage 526 | target: /usr/src/flowforge-file-server/etc/flowforge-storage.yml 527 | volumes: 528 | - fileStorage:/usr/src/flowforge-file-server/var/root 529 | depends_on: 530 | - "forge" 531 | healthcheck: 532 | test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3001/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1) }).on('error', () => process.exit(1))"] 533 | interval: 5s 534 | timeout: 5s 535 | retries: 3 536 | start_period: 10s 537 | 538 | acme: 539 | image: nginxproxy/acme-companion:2.4.0 540 | networks: 541 | - flowforge 542 | restart: always 543 | volumes: 544 | - "/var/run/docker.sock:/var/run/docker.sock:ro" 545 | - "acme:/etc/acme.sh" 546 | volumes_from: 547 | - nginx:rw 548 | environment: 549 | - "DEFAULT_EMAIL=mail@${DOMAIN:?error}" 550 | depends_on: 551 | - "nginx" 552 | profiles: 553 | - autotls 554 | 555 | # npm: 556 | # image: "verdaccio/verdaccio:5" 557 | # networks: 558 | # - flowforge 559 | # restart: always 560 | # environment: 561 | # - "VIRTUAL_HOST=npm.${DOMAIN:?error}" 562 | # - "VIRTUAL_PORT=4873" 563 | # volumes: 564 | # - "./npm/conf:/verdaccio/conf" 565 | # - "./npm/storage:/verdaccio/storage" 566 | # depends_on: 567 | # - nginx 568 | 569 | networks: 570 | flowforge: 571 | 572 | volumes: 573 | flowfuse-persistent-storage: 574 | db: 575 | nginx-proxy-certs: 576 | nginx-proxy-html: 577 | fileStorage: 578 | acme: 579 | emqx: 580 | -------------------------------------------------------------------------------- /etc/flowforge-storage.yml: -------------------------------------------------------------------------------- 1 | port: 3001 2 | host: 0.0.0.0 3 | base_url: http://forge:3000 4 | driver: 5 | type: localfs 6 | quota: 104857600 7 | options: 8 | root: var/root 9 | context: 10 | type: sequelize 11 | quota: 1048576 12 | options: 13 | type: postgres 14 | host: postgres 15 | database: ff-context 16 | username: forge 17 | password: secret 18 | -------------------------------------------------------------------------------- /etc/flowforge.yml: -------------------------------------------------------------------------------- 1 | port: 3000 2 | host: 0.0.0.0 3 | domain: example.com 4 | base_url: http://forge.example.com 5 | api_url: http://forge:3000 6 | 7 | ################################################# 8 | # Database Configuration # 9 | ################################################# 10 | 11 | db: 12 | logging: false 13 | ## The database type: sqlite|postgres 14 | type: postgres 15 | ### SQLite options 16 | 17 | ## The database filename. Relative to $FLOWFORGE_HOME/var/ 18 | ## Set to ':memory:' for a database that is wiped on restart 19 | # storage: forge.db 20 | 21 | ### Postgres options 22 | 23 | host: postgres 24 | user: forge 25 | password: secret 26 | 27 | ################################################# 28 | # Project Driver Configuration # 29 | ################################################# 30 | 31 | driver: 32 | ## The type of backend driver to use 33 | ## Can be: localfs/docker/stub/k8s 34 | type: docker 35 | 36 | options: 37 | ## Options to be passed to the driver 38 | 39 | ### LocalFS options 40 | 41 | ## The first port number to assign to projects 42 | # start_port: 7880 43 | 44 | ## Path to find node executable if not on the default path 45 | # node_path: /usr/bin/node 46 | 47 | ### Docker options 48 | 49 | ## Docker socket path 50 | socket: /tmp/docker.sock 51 | # registry: hub.flowforge.com 52 | 53 | ### K8S options 54 | 55 | ## Kubectl conf file to contact the cluster 56 | # config_file: /opt/share/projects/flowforge/test/config 57 | # registry: hub.flowforge.com 58 | 59 | ################################################# 60 | # Email Configuration # 61 | ################################################# 62 | 63 | email: 64 | enabled: false 65 | debug: false 66 | smtp: 67 | host: localhost 68 | port: 587 69 | secure: false 70 | #auth: 71 | # user: username 72 | # pass: password 73 | 74 | broker: 75 | url: mqtt://flowforge-broker:1883 76 | public_url: ws://mqtt.example.com 77 | 78 | fileStore: 79 | url: http://file-server:3001 -------------------------------------------------------------------------------- /file-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | ARG REGISTRY 4 | ARG REGISTRY_TOKEN 5 | ARG TAG 6 | RUN if [[ ! -z "$REGISTRY_TOKEN" ]]; then echo "//$REGISTRY/:_authToken=$REGISTRY_TOKEN" >> ~/.npmrc ; fi 7 | RUN if [[ ! -z "$REGISTRY" ]] ; then npm config set @flowfuse:registry "https://$REGISTRY"; fi 8 | 9 | WORKDIR /usr/src/flowforge-file-server 10 | RUN mkdir app bin etc var 11 | COPY package.json /usr/src/flowforge-file-server/app 12 | WORKDIR /usr/src/flowforge-file-server/app 13 | RUN npm install --production --no-audit --no-fund 14 | ENV FLOWFORGE_HOME=/usr/src/flowforge-file-server 15 | 16 | LABEL org.label-schema.name="FlowFuse File Storage" \ 17 | org.label-schema.url="https://flowfuse.com" \ 18 | org.label-schema.vcs-type="Git" \ 19 | org.label-schema.vcs-url="https://github.com/FlowFuse/file-server" \ 20 | org.label-schema.docker.dockerfile="flowforge-container/Dockerfile" \ 21 | org.schema-label.description="Collaborative, low code integration and automation environment" \ 22 | authors="FlowFuse Inc." 23 | 24 | EXPOSE 3001 25 | 26 | CMD ["./node_modules/.bin/ff-file-storage"] -------------------------------------------------------------------------------- /file-server/README.md: -------------------------------------------------------------------------------- 1 | # FlowFuse File Server 2 | 3 | This container provides the backing for the FlowFuse replacement Node-RED File nodes. 4 | 5 | It supports storing files either using a mounted volume or a S3 bucket 6 | 7 | ## Configuration 8 | 9 | The container is configured by mounting a `flowforge-storage.yml` file on `/usr/src/flowforge-file-server/etc/flowforge-storage.yml` -------------------------------------------------------------------------------- /file-server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flowfuse/file-server-container", 3 | "version": "2.18.0", 4 | "private": true, 5 | "dependencies": { 6 | "@flowfuse/file-server": "^2.18.0" 7 | }, 8 | "license": "Apache-2.0" 9 | } 10 | -------------------------------------------------------------------------------- /flowforge-docker-local-dev/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:16-alpine 2 | 3 | ARG REGISTRY 4 | RUN if [[ ! -z "$REGISTRY" ]] ; then npm config set @flowforge:registry "$REGISTRY"; fi 5 | 6 | RUN apk add --no-cache --virtual build-base g++ make py3-pip sqlite-dev python2 7 | 8 | WORKDIR /data/flowforge 9 | ENV FLOWFORGE_HOME=/data/flowforge 10 | 11 | EXPOSE 3000 12 | 13 | CMD npm run serve -------------------------------------------------------------------------------- /flowforge-docker-local-dev/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flowfuse/docker-local-dev", 3 | "description": "FlowForge in Docker", 4 | "version": "0.3.0", 5 | "private": true, 6 | "author": { 7 | "name": "FlowForge Inc." 8 | }, 9 | "dependencies": { 10 | "@flowfuse/flowfuse": "file:/data/flowfuse", 11 | "@flowfuse/driver-docker": "file:/data/driver-docker", 12 | "pg": "^8.7.1", 13 | "pg-hstore": "^2.3.4" 14 | }, 15 | "engines": { 16 | "node": ">=16.x" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /flowforge-docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | ARG REGISTRY 4 | ARG REGISTRY_TOKEN 5 | RUN if [[ ! -z "$REGISTRY_TOKEN" ]]; then echo "//$REGISTRY/:_authToken=$REGISTRY_TOKEN" >> ~/.npmrc ; fi 6 | RUN if [[ ! -z "$REGISTRY" ]] ; then npm config set @flowfuse:registry "https://$REGISTRY"; fi 7 | 8 | RUN apk add --no-cache --virtual build-base g++ make py3-pip sqlite-dev python3 git 9 | 10 | WORKDIR /usr/src/forge 11 | RUN mkdir app bin etc var 12 | WORKDIR /usr/src/forge/var 13 | COPY install-device-cache.sh . 14 | RUN ./install-device-cache.sh && rm install-device-cache.sh 15 | WORKDIR /usr/src/forge 16 | COPY package.json /usr/src/forge/app 17 | WORKDIR /usr/src/forge/app 18 | RUN npm install --production --no-audit --no-fund 19 | 20 | ENV FLOWFORGE_HOME=/usr/src/forge 21 | 22 | LABEL org.label-schema.name="FlowFuse Docker" \ 23 | org.label-schema.url="https://flowfuse.com" \ 24 | org.label-schema.vcs-type="Git" \ 25 | org.label-schema.vcs-url="https://github.com/FlowFuse/docker-compose" \ 26 | org.label-schema.docker.dockerfile="flowforge-docker/Dockerfile" \ 27 | org.schema-label.description="Collaborative, low code integration and automation environment" \ 28 | authors="FlowFuse Inc." 29 | 30 | EXPOSE 3000 31 | 32 | CMD ["./node_modules/.bin/flowforge"] 33 | -------------------------------------------------------------------------------- /flowforge-docker/install-device-cache.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -x 2 | 3 | VERSION_LIST="4.0.0 4.0.1 4.0.2 4.0.3 4.0.4 4.0.5 4.0.7 4.0.8 4.0.9" 4 | 5 | mkdir -p device/cache 6 | cd device/cache 7 | 8 | pwd 9 | 10 | for V in $VERSION_LIST 11 | do 12 | echo $V 13 | mkdir $V 14 | ls -l 15 | npm install --omit=dev --omit=optional --no-audit --no-fund --prefix "$V" "@node-red/editor-client@$V" 16 | done 17 | 18 | pwd 19 | ls -l 20 | du -sh 21 | -------------------------------------------------------------------------------- /flowforge-docker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@flowfuse/docker-deployment", 3 | "description": "FlowFuse in Docker", 4 | "version": "2.18.0", 5 | "private": true, 6 | "author": { 7 | "name": "FlowForge Inc." 8 | }, 9 | "dependencies": { 10 | "@flowfuse/flowfuse": "^2.18.0", 11 | "@flowfuse/driver-docker": "^2.18.0", 12 | "pg": "^8.7.1", 13 | "pg-hstore": "^2.3.4" 14 | }, 15 | "engines": { 16 | "node": ">=16.x" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /nginx/my-proxy.conf: -------------------------------------------------------------------------------- 1 | client_max_body_size 5m; -------------------------------------------------------------------------------- /node-red-container/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nodered/node-red:3.1.15-18 2 | 3 | ARG REGISTRY 4 | ARG REGISTRY_TOKEN 5 | ARG BUILD_TAG=latest 6 | RUN if [[ ! -z "$REGISTRY_TOKEN" ]]; then echo "//$REGISTRY/:_authToken=$REGISTRY_TOKEN" >> ~/.npmrc ; fi 7 | RUN if [[ ! -z "$REGISTRY" ]] ; then npm config set @flowfuse:registry "https://$REGISTRY"; fi 8 | 9 | COPY healthcheck.js /healthcheck.js 10 | 11 | COPY package.json /data 12 | WORKDIR /data 13 | RUN mkdir node_modules 14 | RUN npm install 15 | 16 | USER root 17 | RUN mkdir -p /usr/local/ssl-certs 18 | 19 | WORKDIR /usr/src/flowforge-nr-launcher 20 | RUN chown -R node-red:node-red /usr/src/flowforge-nr-launcher 21 | RUN ln -s /usr/src/flowforge-nr-launcher /usr/src/flowfuse-nr-launcher 22 | 23 | USER node-red 24 | RUN npm install @flowfuse/nr-launcher@${BUILD_TAG} 25 | 26 | USER root 27 | RUN mkdir -p /data/storage 28 | RUN chmod -R g+w /data/* /data/.npm/* 29 | RUN chown -R node-red:root /data/* /data/.npm/* 30 | 31 | USER node-red 32 | 33 | ENV NODE_PATH=/usr/src/node-red 34 | ENV HOME=/usr/src/node-red 35 | 36 | EXPOSE 2880 37 | 38 | ENTRYPOINT ["./node_modules/.bin/flowfuse-node-red", "-p", "2880", "-n", "/usr/src/node-red"] 39 | -------------------------------------------------------------------------------- /node-red-container/healthcheck.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var https = require('https'); 3 | var request; 4 | 5 | process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; 6 | 7 | var options = { 8 | host : "localhost", 9 | port : 2880, 10 | timeout : 4000 11 | }; 12 | 13 | request = http.request(options, (res) => { 14 | //console.log(`STATUS: ${res.statusCode}`); 15 | if ((res.statusCode >= 200) && (res.statusCode < 500)) { process.exit(0); } 16 | else { process.exit(1); } 17 | }); 18 | 19 | request.on('error', function(err) { 20 | //console.log('ERROR',err); 21 | process.exit(1); 22 | }); 23 | 24 | request.end(); 25 | -------------------------------------------------------------------------------- /node-red-container/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-red-project", 3 | "description": "A Node-RED Project", 4 | "version": "2.18.0", 5 | "private": true, 6 | "dependencies": {} 7 | } 8 | -------------------------------------------------------------------------------- /npm/README.md: -------------------------------------------------------------------------------- 1 | # FlowFuse offline NPM repository 2 | 3 | The following instructions are for using FlowFuse and the FlowFuse Device Agent 4 | on a network with no access to the Internet. It provides a way to install 5 | Node-RED and Node-RED nodes into both "cloud" Instances and Device Instances. 6 | 7 | It works by providing a cache of the required NPM modules in a private NPM 8 | repository. You will need to populate this repository from a machine that has 9 | access to the Internet. 10 | 11 | ## Creating cache 12 | 13 | These tasks must be done one a machine with access to the Internet and has 14 | docker installed. 15 | 16 | 1. Copy the content of the `npm/online` directory to the Internet connected machine 17 | 2. If needed edit the `npm/online/temp/package.json` or the `npm/online/populate.sh` 18 | script to add any extra nodes that need to be cached. 19 | 3. From the `npm/online` directory run the `./populate.sh` script 20 | 4. Copy the `npm-cache.tgz` to the offline machine into the `npm` directory 21 | 5. run `sudo tar -zxf npm-cache.tgz` to unpack the cache into the `npm/storage` directory 22 | 23 | This step should be repeated any time new nodes are required or before any upgrades or 24 | deploying new versions of the Device Agent. 25 | 26 | ## Enabling on the offline machine 27 | 28 | Edit the `docker-compose.yml` and uncomment the following lines: 29 | 30 | ``` 31 | npm: 32 | image: "verdaccio/verdaccio:5" 33 | networks: 34 | - flowforge 35 | restart: always 36 | environment: 37 | - "VIRTUAL_HOST=npm.example.com" 38 | - "VIRTUAL_PORT=4873" 39 | volumes: 40 | - "./npm/conf:/verdaccio/conf" 41 | - "./npm/storage:/verdaccio/storage" 42 | depends_on: 43 | - nginx 44 | ``` 45 | 46 | You should also update the value of the `VIRTUAL_HOST` entry appropriately to 47 | use the domain already configured with a wild card domain in your local DNS. 48 | 49 | Once complete you can start the npm registry container by running: 50 | 51 | ``` 52 | docker compose up -d 53 | ``` 54 | 55 | ## Configuring FlowFuse 56 | 57 | As an FlowFuse Administrator open the "Admin Settings" page and navigate to 58 | the Templates section. 59 | 60 | Under each Template's Palette section add the following line to the 61 | `NPM configuration file` 62 | 63 | ``` 64 | registry=http://npm.example.com 65 | ``` 66 | 67 | Changing the hostname to match the one provided in the `VIRTUAL_HOST` entry 68 | set when changing the `docker-compose.yml` file. 69 | 70 | Be aware that this will only apply to any newly created Instances, you will 71 | have to manually edit the settings of any existing Instances. 72 | 73 | ## Installing the Device Agent 74 | 75 | When installing the device agent on a machine in the offline network the 76 | registry hostname should be passed to npm 77 | 78 | ``` 79 | npm install --registry=http://npm.example.com -g @flowfuse/device-agent 80 | ``` 81 | 82 | Again changing `npm.example.com` for the correct host name. -------------------------------------------------------------------------------- /npm/conf/config.yaml: -------------------------------------------------------------------------------- 1 | storage: /verdaccio/storage 2 | web: 3 | enabled: false 4 | packages: 5 | '@*/*': 6 | access: $all 7 | '**': 8 | access: $all 9 | log: 10 | - {type: stdout, format: pretty, level: http} -------------------------------------------------------------------------------- /npm/online/.gitignore: -------------------------------------------------------------------------------- 1 | temp/node_modules 2 | temp/cache 3 | temp/package-lock.json 4 | npm-cache.tgz 5 | storage/* 6 | -------------------------------------------------------------------------------- /npm/online/conf/config.yaml: -------------------------------------------------------------------------------- 1 | storage: /verdaccio/storage 2 | web: 3 | enabled: false 4 | uplinks: 5 | npmjs: 6 | url: https://registry.npmjs.org 7 | packages: 8 | '@*/*': 9 | access: $all 10 | proxy: npmjs 11 | '**': 12 | access: $all 13 | proxy: npmjs 14 | log: 15 | - {type: stdout, format: pretty, level: http} -------------------------------------------------------------------------------- /npm/online/populate.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | docker run --rm -d -v "%cd%\conf":/verdaccio/conf -v "%cd%\storage":/verdaccio/storage -p 4873:4873 --name npm-cache verdaccio/verdaccio:5 4 | 5 | timeout /T 5 /NOBREAK 6 | 7 | cd temp 8 | rmdir /S /Q node_modules 9 | rmdir /S /Q cache 10 | md cache 11 | SET npm_config_cache=cache 12 | del package-lock.json 13 | pause 14 | call npm --registry=http://localhost:4873 install 15 | call npm --registry=http://localhost:4873 install node-red@latest 16 | call npm --registry=http://localhost:4873 install node-red@3.1.5 17 | call npm --registry=http://localhost:4873 install node-red@3.0.2 18 | 19 | rmdir /S /Q node_modules 20 | rmdir /S /Q cache 21 | del package-lock.json 22 | 23 | cd .. 24 | 25 | docker stop npm-cache 26 | 27 | tar -zcf npm-cache.tgz storage -------------------------------------------------------------------------------- /npm/online/populate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker run --rm -d -v `pwd`/conf:/verdaccio/conf -v `pwd`/storage:/verdaccio/storage -p 4873:4873 --name npm-cache verdaccio/verdaccio:5 4 | 5 | sleep 5 6 | 7 | cd temp 8 | rm package-lock.json 9 | rm -rf node_modules 10 | rm -rf cache 11 | mkdir cache 12 | export npm_config_cache=cache 13 | npm --registry=http://localhost:4873 install 14 | npm --registry=http://localhost:4873 install node-red@latest 15 | npm --registry=http://localhost:4873 install node-red@3.1.5 16 | npm --registry=http://localhost:4873 install node-red@3.0.2 17 | 18 | rm -rf node_modules 19 | rm -rf cache 20 | rm package-lock.json 21 | 22 | cd .. 23 | 24 | docker stop npm-cache 25 | 26 | tar -zcf npm-cache.tgz storage -------------------------------------------------------------------------------- /npm/online/temp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "populate-cache", 3 | "version": "1.0.0", 4 | "private": true, 5 | "description": "A list of nodes to populate offline cache", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "author": "FlowForge Inc.", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "@flowfuse/device-agent": "^2.2.0", 14 | "@flowfuse/driver-docker": "^2.0.0", 15 | "@flowfuse/flowfuse": "^2.0.1", 16 | "@flowfuse/node-red-dashboard": "latest", 17 | "@flowforge/nr-tools-plugin": "latest", 18 | "nrlint": "latest", 19 | "node-red-debugger": "latest", 20 | "node-red-node-email": "latest", 21 | "node-red-node-mysql": "latest", 22 | "node-red-node-base64": "latest", 23 | "node-red-contrib-postgresql": "latest", 24 | "node-red-contrib-buffer-parser": "latest", 25 | "node-red-contrib-influxdb": "latest", 26 | "node-red-contrib-omron-fins": "latest", 27 | "node-red-contrib-mcprotocol": "latest" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /setup-context-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 6 | SELECT 'CREATE DATABASE "ff-context"' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'ff-context')\gexec 7 | GRANT ALL PRIVILEGES ON DATABASE "ff-context" TO "forge"; 8 | ESQL -------------------------------------------------------------------------------- /setup-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | psql -v ON_ERROR_STOP=1 -U root <<-ESQL 6 | CREATE USER forge WITH PASSWORD 'secret'; 7 | CREATE DATABASE flowforge; 8 | GRANT ALL PRIVILEGES ON DATABASE flowforge TO forge; 9 | ESQL --------------------------------------------------------------------------------