IoT Platform
    MQTT + CoAP

    Deploy ThingsBoard on a VPS

    Self-host ThingsBoard, an IoT platform for device management, data collection, and visualization, on a RamNode VPS with Docker Compose and PostgreSQL.

    ThingsBoard is an IoT platform for device management, data collection, visualization, and processing. It's the heaviest of the three to run — plan your VPS sizing accordingly.

    Target environment: Ubuntu 24.04 LTS, 4 vCPU / 8 GB RAM minimum for a real workload (it will limp along on 4 GB for testing only), 40 GB+ disk. This guide uses the official single-node Docker Compose setup with PostgreSQL.


    1. Initial server prep

    shell
    apt update && apt upgrade -y
    apt install -y curl ufw
    
    adduser deploy
    usermod -aG sudo deploy
    
    ufw allow OpenSSH
    ufw allow 80/tcp
    ufw allow 443/tcp
    ufw allow 1883/tcp    # MQTT
    ufw allow 5683/udp    # CoAP
    ufw allow 7070/tcp    # optional: direct app access during setup
    ufw enable

    Re-login as deploy.

    2. Install Docker

    shell
    curl -fsSL https://get.docker.com | sudo sh
    sudo usermod -aG docker $USER
    newgrp docker

    3. Set kernel/vm settings ThingsBoard expects

    ThingsBoard's bundled Postgres/Cassandra-adjacent services want a higher vm.max_map_count:

    shell
    sudo sysctl -w vm.max_map_count=262144
    echo 'vm.max_map_count=262144' | sudo tee -a /etc/sysctl.conf

    4. Set up the project

    shell
    mkdir -p ~/thingsboard
    cd ~/thingsboard
    mkdir -p .mytb-data .mytb-logs
    sudo chown -R 799:799 .mytb-data .mytb-logs

    (UID/GID 799 is the user ThingsBoard's container runs as internally — matching ownership avoids permission errors on the volumes.)

    5. Create the Compose file

    shell
    nano docker-compose.yml
    shell
    services:
      postgres:
        image: postgres:16
        restart: unless-stopped
        environment:
          POSTGRES_DB: thingsboard
          POSTGRES_PASSWORD: "CHANGE_ME_DB_PASSWORD"
        volumes:
          - ./.postgres-data:/var/lib/postgresql/data
        networks: [tb]
    
      mytb:
        image: thingsboard/tb-postgres:latest
        restart: unless-stopped
        depends_on: [postgres]
        ports:
          - "127.0.0.1:8080:9090"
          - "1883:1883"
          - "7070:7070"
          - "5683-5688:5683-5688/udp"
        environment:
          TB_QUEUE_TYPE: in-memory
          SPRING_DATASOURCE_URL: "jdbc:postgresql://postgres:5432/thingsboard"
          SPRING_DATASOURCE_USERNAME: postgres
          SPRING_DATASOURCE_PASSWORD: "CHANGE_ME_DB_PASSWORD"
        volumes:
          - ./.mytb-data:/data
          - ./.mytb-logs:/var/log/thingsboard
        networks: [tb]
    
    networks:
      tb:
        driver: bridge

    Replace CHANGE_ME_DB_PASSWORD in both places with a real password. Note port 8080 (the web UI) is bound to localhost only — Nginx will expose it publicly. MQTT (1883), the device provisioning port (7070), and CoAP (5683/udp) are left open directly since IoT devices will connect to those.

    6. Run first-time install, then start the stack

    ThingsBoard's image needs an install/upgrade step run once before first launch:

    shell
    docker compose run --rm mytb sh -c "/usr/share/thingsboard/bin/install/install.sh --loadDemo"

    Drop --loadDemo if you don't want sample dashboards/devices seeded. This step can take a few minutes.

    Then start the full stack:

    shell
    docker compose up -d
    docker compose logs -f mytb

    Wait for a log line indicating the server has started, then Ctrl+C.

    7. Put Nginx + TLS in front of the web UI

    shell
    sudo apt install -y nginx certbot python3-certbot-nginx
    sudo nano /etc/nginx/sites-available/thingsboard
    shell
    server {
        listen 80;
        server_name tb.yourdomain.com;
    
        location / {
            proxy_pass http://127.0.0.1:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_read_timeout 3600s;
        }
    }
    shell
    sudo ln -s /etc/nginx/sites-available/thingsboard /etc/nginx/sites-enabled/
    sudo nginx -t && sudo systemctl reload nginx
    sudo certbot --nginx -d tb.yourdomain.com

    8. First login

    Visit https://tb.yourdomain.com. Default credentials (change these immediately):

    • System Administrator: sysadmin@thingsboard.org / sysadmin
    • Tenant Administrator: tenant@thingsboard.org / tenant
    • Customer User: customer@thingsboard.org / customer

    Log in as sysadmin first and change all default passwords before connecting any real devices.

    9. Connecting a test device (sanity check)

    Using MQTT from the VPS itself or another machine:

    shell
    mosquitto_pub -d -q 1 -h tb.yourdomain.com -p 1883 \
      -t "v1/devices/me/telemetry" \
      -u "DEVICE_ACCESS_TOKEN" \
      -m "{\"temperature\": 22.5}"

    Grab a device access token from Tenant Admin → Devices → (create a device) → Copy access token. Confirm the telemetry shows up on the device's "Latest telemetry" tab.

    10. Backups

    Back up:

    • ~/thingsboard/.postgres-data (all entities, users, device metadata, and — if using default in-memory queue with Postgres storage — telemetry too)
    • ~/thingsboard/.mytb-data (attributes/cache data ThingsBoard itself manages)
    shell
    docker compose stop mytb   # avoid backing up mid-write; postgres can stay up briefly
    tar -czf tb-backup-$(date +%F).tar.gz -C ~/thingsboard .postgres-data .mytb-data
    docker compose start mytb

    For a production deployment with meaningful telemetry volume, consider migrating to Cassandra or TimescaleDB for the timeseries store rather than plain Postgres — the default setup here is fine for moderate device counts but won't scale as gracefully.

    11. Updating

    shell
    cd ~/thingsboard
    docker compose pull
    docker compose stop mytb
    docker compose run --rm mytb sh -c "/usr/share/thingsboard/bin/install/install.sh --upgrade"
    docker compose up -d

    Always check the ThingsBoard upgrade notes for your specific version jump — some releases require intermediate upgrade steps rather than jumping straight to latest.