VPS for Docker IoT: Run the Full Stack in Docker Compose

Quick answer

The standard IoT stack (Mosquitto MQTT, Node-RED, InfluxDB, Grafana) runs in Docker Compose on a 4 vCPU / 4GB RAM VPS for up to 1,000 devices. One docker-compose.yml file defines the entire stack. Each service runs in its own container, isolated and independently restartable. The same pattern works on any VPS provider.

Why Docker for IoT on a VPS

Running MQTT, Node-RED, Grafana and InfluxDB directly on the host operating system creates dependency conflicts and makes updates risky. Docker solves this by isolating each service in its own container with its own dependencies.

Practical benefits for IoT deployments:

  • Update each service independently without risk to others
  • Restart a single service without affecting the rest
  • Reproduce the exact same stack on a new VPS from one YAML file
  • Version-pin services for stability (pinning Node-RED 3.1 while upgrading Grafana, for example)

The standard IoT Docker Compose stack

version: "3.8"
services:

  mosquitto:
    image: eclipse-mosquitto:2
    container_name: mosquitto
    restart: unless-stopped
    ports:
      - "1883:1883"
      - "8883:8883"
    volumes:
      - ./mosquitto/config:/mosquitto/config
      - ./mosquitto/data:/mosquitto/data
      - ./mosquitto/log:/mosquitto/log
      - /etc/letsencrypt:/etc/letsencrypt:ro

  node-red:
    image: nodered/node-red:3-minimal
    container_name: node-red
    restart: unless-stopped
    ports:
      - "1880:1880"
    volumes:
      - ./node-red-data:/data
    depends_on:
      - mosquitto

  influxdb:
    image: influxdb:2
    container_name: influxdb
    restart: unless-stopped
    ports:
      - "8086:8086"
    volumes:
      - ./influxdb-data:/var/lib/influxdb2
    environment:
      - DOCKER_INFLUXDB_INIT_MODE=setup
      - DOCKER_INFLUXDB_INIT_USERNAME=admin
      - DOCKER_INFLUXDB_INIT_PASSWORD=CHANGE_ME
      - DOCKER_INFLUXDB_INIT_ORG=iotvps
      - DOCKER_INFLUXDB_INIT_BUCKET=sensors

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "3000:3000"
    volumes:
      - ./grafana-data:/var/lib/grafana
    depends_on:
      - influxdb
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=CHANGE_ME
# Start the stack
docker compose up -d

# Check all services
docker compose ps

# View logs
docker compose logs -f mosquitto

VPS sizing for the Docker IoT stack

Device count Message rate VPS specification
1-50 devices Low 2 vCPU, 2GB RAM, 30GB NVMe
50-500 devices Moderate 4 vCPU, 4GB RAM, 80GB NVMe
500-2,000 devices Moderate 4-8 vCPU, 8GB RAM, 160GB NVMe

InfluxDB and Grafana are the most resource-intensive components. InfluxDB benefits from NVMe storage for write performance. Grafana’s dashboard queries become slow at high retention periods with large datasets without downsampling in InfluxDB.

Watch out

Never expose Docker container ports directly to the internet without firewall protection. Docker bypasses UFW rules by default on some configurations. Use iptables INPUT chain rules or Nginx as a reverse proxy to control access.

Adding WireGuard to the Docker stack

WireGuard should run as a system service (not in Docker) on the VPS host, so it remains available even if Docker itself has issues. Install WireGuard on the host operating system as described in the VPS for WireGuard guide.

The Docker containers communicate with each other on the internal Docker network. The host WireGuard interface can route traffic to them by their internal IP addresses or service names.

Persistent data and backups

All data directories (mosquitto/data, node-red-data, influxdb-data, grafana-data) are mounted as local volumes on the VPS. Back these up with restic to a separate S3-compatible storage. The backup schedule and verification pattern is covered in Backup Strategies for Self-Hosted IoT Infrastructure.

Monitoring the Docker stack

Add Uptime Kuma as a fifth container in the Docker Compose stack to monitor the other services:

  uptime-kuma:
    image: louislam/uptime-kuma:2
    container_name: uptime-kuma
    restart: unless-stopped
    ports:
      - "3001:3001"
    volumes:
      - ./uptime-kuma-data:/app/data

In Uptime Kuma, add TCP port monitors for each service: MQTT on port 8883, Grafana on port 3000, InfluxDB on port 8086, Node-RED on port 1880. If any container stops responding, Uptime Kuma sends an alert immediately rather than the issue being discovered when a user notices missing data. See VPS Uptime Monitoring for the complete setup.

Production hardening for Docker IoT

Before running this stack in production, apply:

  • Change all default passwords in the docker-compose.yml environment variables
  • Add --network-access=internal where services do not need internet access
  • Restrict Grafana, Node-RED and InfluxDB to listen on localhost only, then use Nginx with HTTPS for external access
  • Run docker compose pull monthly to keep images updated
  • Enable automatic Docker image updates with Watchtower (with caution – pin image versions in production to avoid breaking changes)

Adding ThingsBoard to the stack

ThingsBoard is an all-in-one IoT platform (MQTT broker, rules engine, dashboards) that runs in Docker. It is more resource-intensive (minimum 4 vCPU / 8GB RAM) but replaces Mosquitto, Node-RED and Grafana with a single application. See ThingsBoard on a VPS for the Docker setup.

The complete IoT stack in one command

Save the docker-compose.yml from this guide to your VPS, run docker compose up -d, and within two minutes you have Mosquitto accepting MQTT connections on port 8883, Node-RED’s flow editor on port 1880, Grafana’s dashboard interface on port 3000, and InfluxDB’s API on port 8086. The same configuration file, checked into version control, documents the entire infrastructure as code.

Start your IoT Docker stackLumaDock KVM VPS from £4.53/month. Docker and Docker Compose pre-installed on request. Full root access.
See LumaDock plans →

Resource limits and container isolation

Add resource limits to each service in docker-compose.yml to prevent one container from consuming all available memory on the VPS:

  influxdb:
    ...
    deploy:
      resources:
        limits:
          memory: 1G
        reservations:
          memory: 512M

This is particularly important for InfluxDB and Grafana, which can use significant memory during heavy dashboard loads or data compaction operations. With limits set, a misbehaving container cannot impact the rest of the stack.

Frequently asked questions

Should I use Docker or install services directly on the VPS?

Docker is recommended for production deployments on a VPS. Direct installation is simpler for a quick test but creates dependency management issues at scale. The Docker Compose approach above is the standard for long-running self-hosted IoT stacks.

How do I update a single service without affecting others?

Pull the new image and recreate just that container: docker compose pull grafana && docker compose up -d grafana. The other containers continue running unaffected.

Can all four services run on a 2GB RAM VPS?

For small deployments (under 50 devices, low message rate), yes. Mosquitto uses under 50MB. Node-RED uses 100-200MB. Grafana uses 200-400MB at idle. InfluxDB uses 200-500MB. Total idle usage is around 500MB-1GB, leaving headroom on a 2GB VPS. Performance degrades with heavy dashboard load or high message rates.

How do I access the Node-RED and Grafana editors securely?

Set up Nginx on the VPS as a reverse proxy with Let’s Encrypt HTTPS. Nginx proxies yourdomain.com/grafana to port 3000 and yourdomain.com/nodered to port 1880. Both services should be configured to require login. Do not expose ports 1880 or 3000 directly to the internet.

What happens to my data if I need to move to a different VPS?

Tar the data volumes, copy them to the new VPS, and start the Docker Compose stack pointing at the same directories. Everything (MQTT history, Node-RED flows, InfluxDB data, Grafana dashboards) transfers with the data. The entire migration can be completed in under an hour.

Can I add more services to the Docker Compose stack later?

Yes. Add new service definitions to docker-compose.yml and run docker compose up -d. Existing containers are unaffected if their configuration has not changed. Common additions include ChirpStack for LoRaWAN, Portainer for a Docker management UI, and Uptime Kuma for monitoring.