Ein Vergleich von Open-Source No- und Low-Code Plattformen die man selber hosten kann.
Use Cases
Ich habe mir drei Use Cases als Ziel gesetzt:
- Wir bauen eine Web-App die Daten aus einer API konsumiert und in funktionsgerechten Tabellen darstellt.
- Wir bauen Tabellen aus Excel Dateien und wollen diese schick darstellen.
- Wir sind App-Architekt und wollen eine neue Anwendung mit Workflows erstellen.
Die Kandidaten
Mit Hilfe von AI und Websuche finden wir mögliche Standardlösungen.
| Name | Erste Bewertung nach HP Studium | Lizenz | Letzter Commit | Contributors |
|---|---|---|---|---|
| Budibase | Viele Features in Community Edition gesperrt | Proprietär | 2 Tage | 120 |
| Tooljet | Viele Features in Community Edition gesperrt | AGPL-3.0 license | 2 Tage | 654 |
| Nocobase | Einige Features in Community Edition gesperrt | Proprietär | 5 Tage | 107 |
| Appsmith | Einige Features in Community Edition gesperrt | Apache-2.0 | 2 Tage | 342 |
| Baserow | Einige Features in Community Edition gesperrt | Proprietär | 2 Tage | 52 |
| Grommet | Anderer Use Case, fällt raus | - | ||
| Sail | Anderer Use Case, fällt raus | - | ||
| rowy | Anderer Use Case, fällt raus | - | ||
| Directus | Anderer Use Case, fällt raus | - |
Quick-and-Dirty: Live Demos
Wir probieren alle Tools mal kurz aus.
Dazu erstellen wir entsprechende compose Projekte und fahren diese dann auf unserem Server hoch = Live Demo.
Nocobase
Die Einrichtung von Nocobase ist zunächst sehr simpel.
Nach dem ersten Start werden wir vom Login Screen begrüßt. Der voreingestellte User ist:
Email: admin@nocobase.com Password: admin123.
Zumindest das Passwort sollten wir schnell ändern.
Nun sind wir angemeldet und der nüchterne Startbildschirm sieht so aus:
Die Details zur Konfiguration hier im Unterkapitel Setup, im Kapitel Use Cases gucken wir uns das Tool dann genauer an.
Setup
Den Unterordner “storage” legen wir zuvor an.
compose.yml:
services:
app:
image: nocobase/nocobase:latest-full
restart: unless-stopped
networks:
- default
- caddy
depends_on:
- postgres
environment:
# The application's secret key, used to generate user tokens, etc.
# If APP_KEY is changed, old tokens will also become invalid.
# It can be any random string, and make sure it is not exposed.
- APP_KEY=
# Database type, supports postgres, mysql, mariadb
- DB_DIALECT=postgres
# Database host, can be replaced with the IP of an existing database server
- DB_HOST=postgres
# Database port
- DB_PORT=5432
# Database name
- DB_DATABASE=nocobase
# Database user
- DB_USER=nocobase
# Database password
- DB_PASSWORD=
# Timezone
- TZ=Europe/Berlin
volumes:
- ./storage:/app/nocobase/storage
labels:
- "traefik.enable=true"
- "traefik.docker.network=caddy"
- "traefik.http.routers.noco.rule=Host(`nocobase.handtrixxx.com`)"
- "traefik.http.routers.noco.entrypoints=websecure"
- "traefik.http.routers.noco.tls.certresolver=letsencrypt"
- "traefik.http.routers.noco.service=noco"
- "traefik.http.routers.noco-http.rule=Host(`nocobase.handtrixxx.com`)"
- "traefik.http.routers.noco-http.entrypoints=web"
- "traefik.http.routers.noco-http.middlewares=https-redirect"
- "traefik.http.routers.noco-http.service=noco"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
- "traefik.http.services.noco.loadbalancer.server.port=80"
#ports:
# - "13000:80"
# init: true
# If using an existing database server, postgres service can be omitted
postgres:
image: postgres:16
restart: unless-stopped
command: postgres -c wal_level=logical
environment:
POSTGRES_USER: nocobase
POSTGRES_DB: nocobase
POSTGRES_PASSWORD:
volumes:
- ./storage/db/postgres:/var/lib/postgresql/data
networks:
- default
networks:
caddy:
external: true
Appsmith
Auch Appsmith lässt sich sehr einfach einrichten.
Nach dem ersten Start werden wir von einem Assistenten begrüßt, über den wir unseren ersten User einrichten.
Direkt danach werden wir schon nach einer Datenquelle zum verbinden gefragt:

Anschließend landen wir direkt in einem App Designer:
Die Details zur Konfiguration hier im Unterkapitel Setup, im Kapitel Use Cases gucken wir uns das Tool dann genauer an.
Setup
Den Unterordner “stacks” legen wir zuvor an.
compose.yml
services:
appsmith:
image: index.docker.io/appsmith/appsmith-ce
container_name: appsmith
#ports:
# - "80:80"
# - "443:443"
volumes:
- ./stacks:/appsmith-stacks
networks:
- caddy
restart: unless-stopped
labels:
- com.getarcaneapp.arcane.updater=false
- "traefik.enable=true"
- "traefik.docker.network=caddy"
- "traefik.http.routers.appsmith.rule=Host(`appsmith.handtrixxx.com`)"
- "traefik.http.routers.appsmith.entrypoints=websecure"
- "traefik.http.routers.appsmith.tls.certresolver=letsencrypt"
- "traefik.http.routers.appsmith.service=appsmith"
- "traefik.http.routers.appsmith-http.rule=Host(`appsmith.handtrixxx.com`)"
- "traefik.http.routers.appsmith-http.entrypoints=web"
- "traefik.http.routers.appsmith-http.middlewares=https-redirect"
- "traefik.http.routers.appsmith-http.service=appsmith"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
- "traefik.http.services.appsmith.loadbalancer.server.port=80"
networks:
caddy:
external: true
Tooljet
Tooljet bietet viele Optionen zu Einrichtung.
Nach dem ersten Start werden wir von einem Assistenten begrüßt, über den wir unseren ersten User einrichten. Dieser ist etwas verwirrend, weil er einen zunächst auf die Tooljet Webseite führt, nach erneutem Klick aber wieder direkt zurück zu unserer Installation.
Die UI des System wirkt moderner als z.B. bei Appsmith oder Nocobase, dafür werde ich aber schon im Installationsassistenten nach meinem Unternehmen befragt und noch penetranter ob ich denn nicht eine Trial Version des Cloud Angebots wahrnehmen möchte.
Wenn das alles überwunden ist, dann begrüßt uns ein Designer einer automatisch eingerichteten Beispielapplikation:
Die Details zur Konfiguration des Setup hier im Unterkapitel Setup, im Kapitel Use Cases gucken wir uns das Tool dann genauer an.
Setup
Im Endeffekt sind eine compose.yml und eine .env Datei anzulegen, ebenso wie ein Unterordner “postgres_data”.
compose.yml
name: Tooljet app
services:
tooljet:
tty: true
stdin_open: true
container_name: Tooljet-app
image: tooljet/tooljet:v3.20.126-lts
platform: linux/amd64
restart: always
env_file: .env
#ports:
# - 80:80
networks:
- default
- caddy
depends_on:
- postgres
environment:
SERVE_CLIENT: "true"
PORT: "80"
command: npm run start:prod
labels:
- "traefik.enable=true"
- "traefik.docker.network=caddy"
- "traefik.http.routers.tooljet.rule=Host(`tooljet.handtrixxx.com`)"
- "traefik.http.routers.tooljet.entrypoints=websecure"
- "traefik.http.routers.tooljet.tls.certresolver=letsencrypt"
- "traefik.http.routers.tooljet.service=tooljet"
- "traefik.http.routers.tooljet-http.rule=Host(`tooljet.handtrixxx.com`)"
- "traefik.http.routers.tooljet-http.entrypoints=web"
- "traefik.http.routers.tooljet-http.middlewares=https-redirect"
- "traefik.http.routers.tooljet-http.service=tooljet"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
- "traefik.http.services.tooljet.loadbalancer.server.port=80"
postgres:
container_name: ${PG_HOST}
image: postgres:16
restart: always
volumes:
- postgres:/var/lib/postgresql/data
env_file: .env
environment:
- POSTGRES_USER=${PG_USER}
- POSTGRES_PASSWORD=${PG_PASS}
networks:
- default
networks:
default:
caddy:
external: true
volumes:
postgres:
driver: local
driver_opts:
o: bind
type: none
device: ${PWD}/postgres_data
certs:
logs:
fallbackcerts:
.env
# Create .env from this example file and replace values for the environment.
# The application expects a separate .env.test for test environment configuration
# Get detailed information about each variable here: https://docs.tooljet.com/docs/setup/env-vars
TOOLJET_HOST=https://tooljet.handtrixxx.com
LOCKBOX_MASTER_KEY=
SECRET_KEY_BASE=
# DATABASE CONFIG
ORM_LOGGING=all
PG_DB=tooljet_production
PG_USER=postgres
PG_HOST=postgresql
PG_PASS=
# The above postgres values is set to its default state. If necessary, kindly modify it according to your personal preference.
# TOOLJET DATABASE
TOOLJET_DB=tooljet_db
TOOLJET_DB_USER=postgres
TOOLJET_DB_HOST=postgresql
TOOLJET_DB_PASS=
PGRST_DB_URI=postgres://postgres:TfNNf9qb5ScO4MZf@postgresql/tooljet_db
PGRST_HOST=localhost:3002
PGRST_JWT_SECRET=
PGRST_SERVER_PORT=3002
PGRST_DB_PRE_CONFIG=postgrest.pre_config
# Checks every 24 hours to see if a new version of ToolJet is available
# (Enabled by default. Set false to disable)
CHECK_FOR_UPDATES=true
# Checks every 24 hours to update app telemetry data to ToolJet hub.
# (Telemetry is enabled by default. Set value to true to disable.)
# DISABLE_TOOLJET_TELEMETRY=false
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# EMAIL CONFIGURATION
DEFAULT_FROM_EMAIL=hello@tooljet.io
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_DOMAIN=
SMTP_PORT=
# DISABLE USER SIGNUPS (true or false). only applicable if Multi-Workspace feature is enabled
DISABLE_SIGNUPS=
# OBSERVABILITY
APM_VENDOR=
SENTRY_DNS=
SENTRY_DEBUG=
# FEATURE TOGGLE
COMMENT_FEATURE_ENABLE=
ENABLE_MULTIPLAYER_EDITING=true
ENABLE_MARKETPLACE_FEATURE=true
# SSO (Applicable only for Multi-Workspace)
SSO_GOOGLE_OAUTH2_CLIENT_ID=
SSO_GIT_OAUTH2_CLIENT_ID=
SSO_GIT_OAUTH2_CLIENT_SECRET=
SSO_GIT_OAUTH2_HOST=
SSO_ACCEPTED_DOMAINS=
SSO_DISABLE_SIGNUPS=
#ONBOARDING
ENABLE_ONBOARDING_QUESTIONS_FOR_ALL_SIGN_UPS=
#session expiry in minutes
USER_SESSION_EXPIRY=2880
#TELEMETRY
DEPLOYMENT_PLATFORM=docker
# Workflow scheduling configuration
# Worker Mode
# Set to 'true' to enable job processing
# Set to 'false' or unset for HTTP-only mode (scaled deployments)
WORKER=true
# Workflow Processor Concurrency (optional)
# Number of workflow jobs processed concurrently per worker
# Default: 5
TOOLJET_WORKFLOW_CONCURRENCY=5
# Workflow Execution Timeout (optional)
# Maximum time (in seconds) for a workflow to execute
# Default: 60 seconds
WORKFLOW_TIMEOUT_SECONDS=60
Baserow
Baserow lässt sich ebenfalls sehr einfach einrichten.
Nach dem ersten Start werden wir von einem Assistenten begrüßt, über den wir unseren ersten User einrichten. Außerdem erstellt der Assistent eine Beispielapplikation anhand typischer Szenarien die wie auswählen dürfen.
Das Ganze lässt sich irgendwann überspringen und der Startbildschirm den wir sehen, sieht dann so aus:
Setup
compose.yml
Das Unterverzeichnis “data” sollte man zuvor erstellen.
services:
baserow:
container_name: baserow
image: baserow/baserow:2.1.6
environment:
BASEROW_PUBLIC_URL: "https://baserow.handtrixxx.com"
#ports:
# - "80:80"
# - "443:443"
volumes:
- ./data:/baserow/data
networks:
- caddy
labels:
- "traefik.enable=true"
- "traefik.docker.network=caddy"
- "traefik.http.routers.baserow.rule=Host(`baserow.handtrixxx.com`)"
- "traefik.http.routers.baserow.entrypoints=websecure"
- "traefik.http.routers.baserow.tls.certresolver=letsencrypt"
- "traefik.http.routers.baserow.service=baserow"
- "traefik.http.routers.baserow-http.rule=Host(`baserow.handtrixxx.com`)"
- "traefik.http.routers.baserow-http.entrypoints=web"
- "traefik.http.routers.baserow-http.middlewares=https-redirect"
- "traefik.http.routers.baserow-http.service=baserow"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
- "traefik.http.services.baserow.loadbalancer.server.port=80"
networks:
caddy:
external: true
Budibase
Budibase ist etwas komplizierter, aber machbar.
compose.yml
Die Labels sind nur erfrderlich, wenn man Traefik nutzen will.
services:
app-service:
restart: unless-stopped
image: budibase/apps:latest
container_name: bbapps
networks:
- default
environment:
SELF_HOSTED: 1
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
WORKER_URL: http://worker-service:4003
MINIO_URL: http://minio-service:9000
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
BUDIBASE_ENVIRONMENT: ${BUDIBASE_ENVIRONMENT}
PORT: 4002
API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
JWT_SECRET: ${JWT_SECRET}
LOG_LEVEL: info
ENABLE_ANALYTICS: "true"
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
REDIS_USERNAME: ${REDIS_USERNAME:-}
LITELLM_URL: http://litellm-service:4000
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
BB_ADMIN_USER_EMAIL: ${BB_ADMIN_USER_EMAIL}
BB_ADMIN_USER_PASSWORD: ${BB_ADMIN_USER_PASSWORD}
PLUGINS_DIR: ${PLUGINS_DIR}
OFFLINE_MODE: ${OFFLINE_MODE:-}
depends_on:
- worker-service
- redis-service
- litellm-service
# volumes:
# - /some/path/to/plugins:/plugins
worker-service:
restart: unless-stopped
image: budibase/worker
container_name: bbworker
networks:
- default
environment:
SELF_HOSTED: 1
PORT: 4003
CLUSTER_PORT: ${MAIN_PORT}
API_ENCRYPTION_KEY: ${API_ENCRYPTION_KEY}
JWT_SECRET: ${JWT_SECRET}
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_URL: http://minio-service:9000
APPS_URL: http://app-service:4002
COUCH_DB_USERNAME: ${COUCH_DB_USER}
COUCH_DB_PASSWORD: ${COUCH_DB_PASSWORD}
COUCH_DB_URL: http://${COUCH_DB_USER}:${COUCH_DB_PASSWORD}@couchdb-service:5984
INTERNAL_API_KEY: ${INTERNAL_API_KEY}
REDIS_URL: redis-service:6379
REDIS_PASSWORD: ${REDIS_PASSWORD}
REDIS_USERNAME: ${REDIS_USERNAME:-}
LITELLM_URL: http://litellm-service:4000
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
OFFLINE_MODE: ${OFFLINE_MODE:-}
depends_on:
- redis-service
- minio-service
- litellm-service
minio-service:
restart: unless-stopped
image: minio/minio
networks:
- default
volumes:
- ./minio_data:/data
environment:
MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY}
MINIO_SECRET_KEY: ${MINIO_SECRET_KEY}
MINIO_BROWSER: "off"
command: server /data --console-address ":9001"
healthcheck:
test: "timeout 5s bash -c ':> /dev/tcp/127.0.0.1/9000' || exit 1"
interval: 30s
timeout: 20s
retries: 3
proxy-service:
restart: unless-stopped
#ports:
# - "${MAIN_PORT}:10000"
container_name: bbproxy
image: budibase/proxy
networks:
- default
- caddy
environment:
- PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND=10
- PROXY_RATE_LIMIT_API_PER_SECOND=50
- APPS_UPSTREAM_URL=http://app-service:4002
- WORKER_UPSTREAM_URL=http://worker-service:4003
- MINIO_UPSTREAM_URL=http://minio-service:9000
- COUCHDB_UPSTREAM_URL=http://couchdb-service:5984
- RESOLVER=127.0.0.11
depends_on:
- minio-service
- worker-service
- app-service
- couchdb-service
labels:
- "traefik.enable=true"
- "traefik.docker.network=caddy"
- "traefik.http.routers.buddy.rule=Host(`buddy.handtrixxx.com`)"
- "traefik.http.routers.buddy.entrypoints=websecure"
- "traefik.http.routers.buddy.tls.certresolver=letsencrypt"
- "traefik.http.routers.buddy.service=buddy"
- "traefik.http.routers.buddy-http.rule=Host(`buddy.handtrixxx.com`)"
- "traefik.http.routers.buddy-http.entrypoints=web"
- "traefik.http.routers.buddy-http.middlewares=https-redirect"
- "traefik.http.routers.buddy-http.service=buddy"
- "traefik.http.middlewares.https-redirect.redirectscheme.scheme=https"
- "traefik.http.middlewares.https-redirect.redirectscheme.permanent=true"
- "traefik.http.services.buddy.loadbalancer.server.port=10000"
couchdb-service:
restart: unless-stopped
image: budibase/database:2.1.0
networks:
- default
environment:
- COUCHDB_PASSWORD=${COUCH_DB_PASSWORD}
- COUCHDB_USER=${COUCH_DB_USER}
- TARGETBUILD=docker-compose
- DATA_DIR=/data
volumes:
- ./couchdb3_data:/data
redis-service:
restart: unless-stopped
image: redis
networks:
- default
command: redis-server --requirepass "${REDIS_PASSWORD}"
volumes:
- ./redis_data:/data
litellm-service:
restart: unless-stopped
image: docker.litellm.ai/berriai/litellm:1.80.15-stable.1
#ports:
# - "${LITELLM_PORT:-4000}:4000"
volumes:
- ./litellm_config.yaml:/app/config.yaml
environment:
DATABASE_URL: "postgresql://${LITELLM_DB_USER}:${LITELLM_DB_PASSWORD}@litellm-db:5432/${LITELLM_DB_NAME}"
STORE_MODEL_IN_DB: "True"
LITELLM_REASONING_AUTO_SUMMARY: "true"
LITELLM_MASTER_KEY: ${LITELLM_MASTER_KEY}
LITELLM_SALT_KEY: ${LITELLM_SALT_KEY}
command: ["--config", "/app/config.yaml"]
depends_on:
- litellm-db
networks:
- default
healthcheck:
test:
[
"CMD-SHELL",
"wget --no-verbose --tries=1 http://localhost:4000/health/liveliness || exit 1",
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
litellm-db:
restart: unless-stopped
image: postgres:16
environment:
POSTGRES_DB: ${LITELLM_DB_NAME}
POSTGRES_USER: ${LITELLM_DB_USER}
POSTGRES_PASSWORD: ${LITELLM_DB_PASSWORD}
volumes:
- ./litellm_data:/var/lib/postgresql/data
networks:
- default
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -d ${LITELLM_DB_NAME} -U ${LITELLM_DB_USER}",
]
interval: 1s
timeout: 5s
retries: 10
networks:
caddy:
external: true
.env
auch wenn der “REDIS_USERNAME” sich setzen lässt, fährt die Anwendung dann nicht ohne weiteres Zutun hoch. Deshalb diesen zunächst als Kommentar stehen lassen.
# Use the main port in the builder for your self hosting URL, e.g. localhost:10000
MAIN_PORT=10000
# This section contains all secrets pertaining to the system
# These should be updated
# #openssl rand -hex 32
API_ENCRYPTION_KEY=
JWT_SECRET=
MINIO_ACCESS_KEY=
MINIO_SECRET_KEY=
COUCH_DB_PASSWORD=
COUCH_DB_USER=
REDIS_PASSWORD=
#REDIS_USERNAME=budiredis
INTERNAL_API_KEY=
LITELLM_MASTER_KEY=
LITELLM_SALT_KEY=
# This section contains variables that do not need to be altered under normal circumstances
APP_PORT=4002
WORKER_PORT=4003
MINIO_PORT=4004
COUCH_DB_PORT=4005
COUCH_DB_SQS_PORT=4006
REDIS_PORT=6379
BUDIBASE_ENVIRONMENT=PRODUCTION
SQL_MAX_ROWS=
LITELLM_PORT=4000
LITELLM_DB_NAME=litellm
LITELLM_DB_USER=llmproxy
LITELLM_DB_PASSWORD=
# An admin user can be automatically created initially if these are set
BB_ADMIN_USER_EMAIL=
BB_ADMIN_USER_PASSWORD=
# A path that is watched for plugin bundles. Any bundles found are imported automatically/
PLUGINS_DIR=
ROLLING_LOG_MAX_SIZE=
litellm_config.yaml
Ohne diese Datei fährt Budibase nicht hoch, der Inhalt muss aber nur semantisch korrekt sein. Also lassen sich die folgenden Beispielwerte einfach 1:1 übernehmen.
model_list:
- model_name: azure-gpt-4o
litellm_params:
model: azure/<your-azure-model-deployment>
api_base: os.environ/AZURE_API_BASE # runs os.getenv("AZURE_API_BASE")
api_key: os.environ/AZURE_API_KEY # runs os.getenv("AZURE_API_KEY")
api_version: "2025-01-01-preview"



