Installing ThingsBoard using Docker (Linux, macOS)
This guide covers a single-node ThingsBoard installation using Docker Compose on Linux or macOS. By the end, you will have a licensed, activated ThingsBoard instance — free, no credit card required — with a system administrator account and, optionally, a demo tenant, running on your machine. For cluster setup, see Cluster Setup with Docker Compose.
Prerequisites
Section titled “Prerequisites”You’ll need outbound internet access to pull Docker images and to reach the ThingsBoard License Portal during activation.
Ensure your server meets the minimum requirements:
| Use case | CPU | RAM | Recommended services |
|---|---|---|---|
| Development / PoC | 1 core | 4 GB | ThingsBoard, PostgreSQL |
| Production (small) | 2 cores | 8 GB | ThingsBoard, PostgreSQL, Kafka |
| Production (recommended) | 4+ cores | 16+ GB | ThingsBoard, PostgreSQL, Kafka, Cassandra |
Install Docker on your server:
Step 1. Create Docker Compose File
Section titled “Step 1. Create Docker Compose File”Create a dedicated directory for your ThingsBoard installation and navigate to it. All subsequent commands in this guide should be run from this directory.
mkdir -p ~/thingsboard && cd ~/thingsboardThingsBoard uses a message queue to route messages between its internal services. Select the option that matches your infrastructure:
- In Memory (default) — built-in queue, no extra setup required. Suitable for development and PoC. Not recommended for production or multi-node deployments.
- Kafka — high-throughput, durable queue. Run it yourself or use a managed service such as AWS MSK.
- Confluent Cloud — fully managed Kafka service. Use this if you want Kafka without managing the infrastructure yourself.
Create the docker-compose.yml file:
nano docker-compose.ymlPaste one of the configurations below, save, and exit. Or use the download button to save the file directly.
services: postgres: restart: always image: "postgres:18" ports: - "5432" environment: POSTGRES_DB: thingsboard POSTGRES_PASSWORD: postgres volumes: - postgres-data:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d thingsboard"] interval: 10s timeout: 5s retries: 5 start_period: 10s thingsboard: restart: always image: "thingsboard/tb-node:4.4.0" ports: - "8080:8080" - "1883:1883" - "8883:8883" - "9090:9090" - "7070:7070" - "5683-5688:5683-5688/udp" logging: driver: "json-file" options: max-size: "100m" max-file: "10" environment: TB_SERVICE_ID: tb-node # TB_LICENSE_SECRET: PUT_YOUR_LICENSE_SECRET_HERE TB_LICENSE_INSTANCE_DATA_FILE: /data/license.data REPORTS_SERVER_ENDPOINT_URL: http://tb-web-report:8383 SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/thingsboard SPRING_DATASOURCE_PASSWORD: postgres # Pre-configured for optional Trendz Analytics integration DEFAULT_TRENDZ_URL: http://trendz:8888 DEFAULT_TB_URL: http://thingsboard:8080 volumes: - license-data:/data depends_on: postgres: condition: service_healthy tb-web-report: restart: always image: "thingsboard/tb-web-report:4.4.0" ports: - "8383" depends_on: - thingsboard environment: HTTP_BIND_ADDRESS: 0.0.0.0 HTTP_BIND_PORT: 8383 LOGGER_LEVEL: info LOG_FOLDER: logs LOGGER_FILENAME: tb-web-report-%DATE%.log DOCKER_MODE: true DEFAULT_PAGE_NAVIGATION_TIMEOUT: 120000 DASHBOARD_IDLE_WAIT_TIME: 3000 USE_NEW_PAGE_FOR_REPORT: truevolumes: postgres-data: name: tb-postgres-data driver: local license-data: name: tb-license-data driver: localServices started:
postgres— PostgreSQL databasethingsboard— ThingsBoard application nodetb-web-report— reporting component for PDF/PNG dashboard exports
This example runs Kafka locally as a Docker container. If you already have a Kafka broker or use a managed service (e.g. AWS MSK), remove the kafka service and update TB_KAFKA_SERVERS to point to your broker.
services: postgres: restart: always image: "postgres:18" ports: - "5432" environment: POSTGRES_DB: thingsboard POSTGRES_PASSWORD: postgres volumes: - postgres-data:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d thingsboard"] interval: 10s timeout: 5s retries: 5 start_period: 10s kafka: restart: always image: bitnamilegacy/kafka:4.0 ports: - 9092:9092 - 9093 environment: ALLOW_PLAINTEXT_LISTENER: "yes" KAFKA_CFG_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093" KAFKA_CFG_ADVERTISED_LISTENERS: "PLAINTEXT://:9092" KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT" KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "PLAINTEXT" KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "false" KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: "1" KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: "1" KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: "1" KAFKA_CFG_PROCESS_ROLES: "controller,broker" KAFKA_CFG_NODE_ID: "0" KAFKA_CFG_CONTROLLER_LISTENER_NAMES: "CONTROLLER" KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: "0@kafka:9093" KAFKA_CFG_LOG_RETENTION_MS: "300000" KAFKA_CFG_SEGMENT_BYTES: "26214400" volumes: - kafka-data:/bitnami thingsboard: restart: always image: "thingsboard/tb-node:4.4.0" ports: - "8080:8080" - "1883:1883" - "8883:8883" - "9090:9090" - "7070:7070" - "5683-5688:5683-5688/udp" logging: driver: "json-file" options: max-size: "100m" max-file: "10" environment: TB_SERVICE_ID: tb-node # TB_LICENSE_SECRET: PUT_YOUR_LICENSE_SECRET_HERE TB_LICENSE_INSTANCE_DATA_FILE: /data/license.data REPORTS_SERVER_ENDPOINT_URL: http://tb-web-report:8383 SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/thingsboard SPRING_DATASOURCE_PASSWORD: postgres # Pre-configured for optional Trendz Analytics integration DEFAULT_TRENDZ_URL: http://trendz:8888 DEFAULT_TB_URL: http://thingsboard:8080 TB_QUEUE_TYPE: kafka TB_KAFKA_SERVERS: kafka:9092 volumes: - license-data:/data depends_on: postgres: condition: service_healthy kafka: condition: service_started tb-web-report: restart: always image: "thingsboard/tb-web-report:4.4.0" ports: - "8383" depends_on: - thingsboard environment: HTTP_BIND_ADDRESS: 0.0.0.0 HTTP_BIND_PORT: 8383 LOGGER_LEVEL: info LOG_FOLDER: logs LOGGER_FILENAME: tb-web-report-%DATE%.log DOCKER_MODE: true DEFAULT_PAGE_NAVIGATION_TIMEOUT: 120000 DASHBOARD_IDLE_WAIT_TIME: 3000 USE_NEW_PAGE_FOR_REPORT: truevolumes: postgres-data: name: tb-postgres-data driver: local license-data: name: tb-license-data driver: local kafka-data: name: tb-kafka-data driver: localServices started:
postgres— PostgreSQL databasekafka— Kafka broker (local, single-node)thingsboard— ThingsBoard application nodetb-web-report— reporting component for PDF/PNG dashboard exports
First create a Confluent Cloud account, create a Kafka cluster, and obtain your API Key. Replace CLUSTER_API_KEY, CLUSTER_API_SECRET, and localhost:9092 with your Confluent Cloud values:
services: postgres: restart: always image: "postgres:18" ports: - "5432" environment: POSTGRES_DB: thingsboard POSTGRES_PASSWORD: postgres volumes: - postgres-data:/var/lib/postgresql healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres -d thingsboard"] interval: 10s timeout: 5s retries: 5 start_period: 10s thingsboard: restart: always image: "thingsboard/tb-node:4.4.0" ports: - "8080:8080" - "1883:1883" - "8883:8883" - "9090:9090" - "7070:7070" - "5683-5688:5683-5688/udp" logging: driver: "json-file" options: max-size: "100m" max-file: "10" environment: TB_SERVICE_ID: tb-node # TB_LICENSE_SECRET: PUT_YOUR_LICENSE_SECRET_HERE TB_LICENSE_INSTANCE_DATA_FILE: /data/license.data REPORTS_SERVER_ENDPOINT_URL: http://tb-web-report:8383 SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/thingsboard SPRING_DATASOURCE_PASSWORD: postgres # Pre-configured for optional Trendz Analytics integration DEFAULT_TRENDZ_URL: http://trendz:8888 DEFAULT_TB_URL: http://thingsboard:8080 TB_QUEUE_TYPE: kafka TB_KAFKA_SERVERS: localhost:9092 TB_QUEUE_KAFKA_REPLICATION_FACTOR: 3 TB_QUEUE_KAFKA_USE_CONFLUENT_CLOUD: true TB_QUEUE_KAFKA_CONFLUENT_SASL_JAAS_CONFIG: 'org.apache.kafka.common.security.plain.PlainLoginModule required username="CLUSTER_API_KEY" password="CLUSTER_API_SECRET";' TB_QUEUE_CORE_POLL_INTERVAL_MS: 1000 TB_QUEUE_CORE_PARTITIONS: 2 TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS: 1000 TB_QUEUE_TRANSPORT_REQUEST_POLL_INTERVAL_MS: 1000 TB_QUEUE_TRANSPORT_RESPONSE_POLL_INTERVAL_MS: 1000 TB_QUEUE_TRANSPORT_NOTIFICATIONS_POLL_INTERVAL_MS: 1000 TB_QUEUE_VC_INTERVAL_MS: 1000 TB_QUEUE_VC_PARTITIONS: 1 volumes: - license-data:/data depends_on: postgres: condition: service_healthy tb-web-report: restart: always image: "thingsboard/tb-web-report:4.4.0" ports: - "8383" depends_on: - thingsboard environment: HTTP_BIND_ADDRESS: 0.0.0.0 HTTP_BIND_PORT: 8383 LOGGER_LEVEL: info LOG_FOLDER: logs LOGGER_FILENAME: tb-web-report-%DATE%.log DOCKER_MODE: true DEFAULT_PAGE_NAVIGATION_TIMEOUT: 120000 DASHBOARD_IDLE_WAIT_TIME: 3000 USE_NEW_PAGE_FOR_REPORT: truevolumes: postgres-data: name: tb-postgres-data driver: local license-data: name: tb-license-data driver: localServices started:
postgres— PostgreSQL databasethingsboard— ThingsBoard application nodetb-web-report— reporting component for PDF/PNG dashboard exports
You can update the default Rule Engine queue configuration using the UI. See Rule Engine Queues for details.
Docker Compose Parameters
Section titled “Docker Compose Parameters”Ports (host:container)
| Port mapping | Description |
|---|---|
8080:8080 | Web UI and REST API. The left value is the host port — change it if 8080 is already in use. |
1883:1883 | MQTT — plaintext IoT device connections |
8883:8883 | MQTT over TLS — encrypted IoT device connections |
5683:5683/udp | CoAP — plaintext IoT protocol |
5684:5684/udp | CoAP over DTLS — encrypted CoAP |
5685:5685/udp | LwM2M CoAP — plaintext Lightweight M2M |
5686:5686/udp | LwM2M CoAP over DTLS — encrypted LwM2M |
5687:5687/udp | LwM2M — plaintext Lightweight M2M (Bootstrap) |
5688:5688/udp | LwM2M over DTLS — encrypted Lightweight M2M (Bootstrap) |
7070:7070 | Edge RPC (gRPC) — connections from ThingsBoard Edge nodes |
9090:9090 | Remote Integration Executor (gRPC) — used by external integration services |
Environment variables
| Variable | Description |
|---|---|
TB_SERVICE_ID | Unique identifier of the ThingsBoard node. Default: tb-node. |
TB_LICENSE_SECRET | Optional. Your ThingsBoard license key. Commented out by default. If you already have a key, uncomment the line and replace PUT_YOUR_LICENSE_SECRET_HERE with it during configuration. If you don’t, leave it commented out and activate the instance from the web UI after the first start. |
TB_LICENSE_INSTANCE_DATA_FILE | Path inside the container where the license instance data is stored. Default: /data/license.data, which is on the tb-license-data volume, so the activation survives container restarts and upgrades. |
REPORTS_SERVER_ENDPOINT_URL | Address of the tb-web-report service used for PDF/PNG dashboard exports. Default: http://tb-web-report:8383. |
POSTGRES_DB | Set on the postgres service. Name of the database created on first start. Default: thingsboard. |
POSTGRES_PASSWORD | Set on the postgres service. Password for the PostgreSQL postgres user. Must match SPRING_DATASOURCE_PASSWORD. Change the default value in production. |
SPRING_DATASOURCE_URL | PostgreSQL JDBC connection URL. Specifies the host and database name. Default: jdbc:postgresql://postgres:5432/thingsboard. |
SPRING_DATASOURCE_PASSWORD | PostgreSQL password ThingsBoard uses to connect. Must match POSTGRES_PASSWORD. |
TB_QUEUE_TYPE | Message queue type. Options: in-memory (default, single-node only), kafka, rabbitmq. Not set in the In Memory configuration, which uses the default. |
TB_KAFKA_SERVERS | Kafka bootstrap servers. Required when TB_QUEUE_TYPE=kafka. Set to kafka:9092 in the Kafka configuration (the local kafka service); replace localhost:9092 with your Confluent Cloud bootstrap server in the Confluent Cloud configuration. |
TB_QUEUE_KAFKA_, | Confluent Cloud configuration only. Enable Confluent Cloud, pass the SASL credentials (CLUSTER_API_KEY, CLUSTER_API_SECRET), and tune the replication factor, poll intervals and partition counts for a managed cluster. |
DEFAULT_TRENDZ_URL | Trendz Analytics endpoint — pre-configured for optional Trendz integration. Default: http://trendz:8888. |
DEFAULT_TB_URL | Address at which other services, such as Trendz, reach ThingsBoard inside the Compose network. Default: http://thingsboard:8080. |
Volumes
| Volume | Description |
|---|---|
tb-postgres-data | Persists PostgreSQL data across container restarts and upgrades. |
tb-kafka-data | Persists Kafka data. Only present when TB_QUEUE_TYPE=kafka. |
tb-license-data | Persists license instance data — prevents license re-activation on each restart. |
For the full list of configuration parameters, see the Configuration Reference.
Step 2. Initialize Database Schema
Section titled “Step 2. Initialize Database Schema”Before starting ThingsBoard, initialize the database schema:
docker compose run --rm -e INSTALL_TB=true thingsboardThe container exits automatically once initialization is complete.
Step 3. Start ThingsBoard
Section titled “Step 3. Start ThingsBoard”Start all containers:
docker compose up -dMonitor the startup. The line confirming the platform is ready will be highlighted:
docker compose logs -f thingsboard | grep --line-buffered --color=always -E 'Started ThingsboardServerApplication|$'Press Ctrl+C to detach from the log stream — containers will continue running in the background.
Access ThingsBoard Web UI
Section titled “Access ThingsBoard Web UI”Open ThingsBoard in a web browser:
http://localhost:8080On first launch you are greeted with the activation screen:
Welcome to ThingsBoard 4.4.0Get your free license and start buildingContinue with the next step to license and activate your instance.
Step 4. License and Activate Your Instance
Section titled “Step 4. License and Activate Your Instance”Opening ThingsBoard in your browser for the first time shows an activation welcome screen.
- Click Open the License Portal, then create an account or sign in.
- Choose a free license: Commercial (up to 100 devices, one server) or Non-commercial (up to 1,000 devices, any number of servers).
- Check the confirmation box to agree to the Terms of Use, Privacy Policy, and ThingsBoard License (BUSL 1.1), then click Accept and activate to issue the license and activate your instance.
- Click Continue setup on your instance to proceed to account creation. Your license key stays available on this screen and in your License Portal account.
- Click Already have a license?, paste your license key, and click Activate.
- Your instance is now activated. The license is saved on this server and in your License Portal account.
Step 5. Create Your Administrator Account
Section titled “Step 5. Create Your Administrator Account”Once activated, ThingsBoard prompts you to create your System Administrator account — the account that manages the platform itself (tenants, system settings, platform-wide resources).
- Enter Email, Password, and Confirm password.
- The Set up a demo tenant option is selected by default: it creates a ready-made tenant with dashboards, devices, and rule chains for exploring the platform before building your own solution. Clear it if you want to start with an empty platform.
- Click Create account. You are signed in as the system administrator.
Your ThingsBoard instance is now installed and running.
[Optional] Install Trendz Analytics
Section titled “[Optional] Install Trendz Analytics”You may optionally install Trendz Analytics at any time. The Trendz compose file extends the main docker-compose.yml — always run them together.
-
Create the Trendz database in PostgreSQL:
Terminal window docker compose -f docker-compose.yml exec -it postgres psql -U postgres -c "CREATE DATABASE trendz;" -
Create the
docker-compose-trendz.ymlfile with the following content:docker-compose-trendz.yml services:trendz:restart: alwaysimage: "thingsboard/trendz:1.16.0"ports:- "8888:8888"environment:TB_API_URL: http://thingsboard:8080SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/trendzSPRING_DATASOURCE_USERNAME: postgresSPRING_DATASOURCE_PASSWORD: postgresSCRIPT_ENGINE_DOCKER_PROVIDER_URL: trendz-python-executor:8181SCRIPT_ENGINE_TIMEOUT: 30000volumes:- trendz-conf:/trendz-config-files- trendz-data:/datadepends_on:postgres:condition: service_healthytrendz-python-executor:restart: alwaysimage: "thingsboard/trendz-python-executor:1.16.0"ports:- "8181:8181"environment:EXECUTOR_MANAGER: 1EXECUTOR_SCRIPT_ENGINE: 6THROTTLING_QUEUE_CAPACITY: 10THROTTLING_THREAD_POOL_SIZE: 6NETWORK_BUFFER_SIZE: 5242880volumes:- trendz-python-executor-conf:/python-executor-config-files- trendz-python-executor-data:/datavolumes:trendz-conf:name: trendz-confdriver: localtrendz-data:name: trendz-datadriver: localtrendz-python-executor-conf:name: trendz-python-executor-confdriver: localtrendz-python-executor-data:name: trendz-python-executor-datadriver: local
Trendz Docker Compose Parameters
Section titled “Trendz Docker Compose Parameters”| Parameter | Description |
|---|---|
8888:8888 |
Trendz HTTP port |
trendz-conf |
Docker volume for Trendz configuration |
trendz-data |
Docker volume for Trendz data |
trendz-python-executor-conf |
Docker volume for Trendz Python executor configuration |
trendz-python-executor-data |
Docker volume for Trendz Python executor data |
Start the Platform with Trendz
Section titled “Start the Platform with Trendz”Bring up all containers (including Trendz) as a single Compose project:
docker compose -f docker-compose.yml -f docker-compose-trendz.yml up -ddocker compose -f docker-compose.yml -f docker-compose-trendz.yml logs -f thingsboardInspect Logs with Trendz
Section titled “Inspect Logs with Trendz”Stream ThingsBoard logs:
docker compose -f docker-compose.yml -f docker-compose-trendz.yml logs -f thingsboardStream Trendz logs:
docker compose -f docker-compose.yml -f docker-compose-trendz.yml logs -f trendzStop all containers:
docker compose -f docker-compose.yml -f docker-compose-trendz.yml downStart all containers:
docker compose -f docker-compose.yml -f docker-compose-trendz.yml up -dInspect Logs and Control Containers
Section titled “Inspect Logs and Control Containers”Stream the ThingsBoard container logs:
docker compose logs -f thingsboardStop all containers:
docker compose downStart all containers:
docker compose up -dTroubleshooting
Section titled “Troubleshooting”DNS Issues
Section titled “DNS Issues”If you observe errors related to DNS issues, for example:
127.0.1.1:53: cannot unmarshal DNS messageConfigure your system to use Google public DNS servers. See Linux and macOS instructions.
Connecting to PostgreSQL on the Docker Host
Section titled “Connecting to PostgreSQL on the Docker Host”Inside a container, localhost and 127.0.0.1 point to the container itself, not to the Docker host. A ThingsBoard container reaches a PostgreSQL instance installed on the host through a different address.
Set the database address
The value of SPRING_DATASOURCE_URL depends on where PostgreSQL runs:
| PostgreSQL location | SPRING_DATASOURCE_URL |
|---|---|
| Service in the same compose file | jdbc:postgresql://postgres:5432/thingsboard |
| Docker host | jdbc:postgresql://host.docker.internal:5432/thingsboard |
| Another server | jdbc:postgresql://SERVER_IP:5432/thingsboard |
On macOS, Docker Desktop resolves host.docker.internal to the host automatically. On Linux, map the alias to the host gateway in every ThingsBoard service that connects to the database:
services: thingsboard: extra_hosts: - "host.docker.internal:host-gateway"Allow the Docker subnet in PostgreSQL
A PostgreSQL instance running on the host accepts local connections only until you configure it otherwise. Find the subnet of your Docker network first:
docker network lsdocker network inspect NETWORK_NAME --format '{{(index .IPAM.Config 0).Subnet}}'Set the listen address in postgresql.conf:
listen_addresses = '*'Add a matching client entry to pg_hba.conf. The database and user must match SPRING_DATASOURCE_URL and SPRING_DATASOURCE_USERNAME, and the subnet must be the one you found. The authentication method must match how the password is stored, so use md5 instead of scram-sha-256 on instances that still store MD5 passwords:
host thingsboard DB_USER 172.18.0.0/16 scram-sha-256Restart PostgreSQL. A reload is enough for pg_hba.conf, but listen_addresses is applied at server start only:
sudo systemctl restart postgresqlVerify the connection
Run psql from a temporary container. It takes the same network path as ThingsBoard, which separates a networking problem from a ThingsBoard configuration problem. Replace DB_USER and DB_PASSWORD with the values from SPRING_DATASOURCE_USERNAME and SPRING_DATASOURCE_PASSWORD:
docker run --rm --add-host=host.docker.internal:host-gateway postgres:18 \ psql "postgresql://DB_USER:DB_PASSWORD@host.docker.internal:5432/thingsboard" -c "SELECT 1;"If this command succeeds and ThingsBoard still fails, the network path is fine and the problem is in the ThingsBoard configuration.
Check for a host firewall
Docker maps its internal networks to the host with iptables rules. Additional firewall software can interfere with those rules and drop the traffic before it reaches PostgreSQL. If connections time out instead of being rejected, check whether a firewall like UFW or firewalld is installed and enabled on the host, and whether its configuration blocks traffic coming from the Docker networks.
Next Steps
Section titled “Next Steps”With ThingsBoard running, these concept guides help you build your first solution:
- Getting Started — a guided walkthrough of the platform after installation.
- Multi-Tenancy & Hierarchy — how tenants, customers, and users are organized.
- Digital Twin Model — how devices and assets are modeled in the platform.
- Data Processing — how the Rule Engine transforms and routes incoming data.
- Alarms & Notifications — how to detect conditions and notify users.
- Data Visualization — how to build dashboards and widgets.
Was this helpful?