Mahbubur Riad
Back to blog
DevOps 7 min read

Step‑By‑Step Docker Compose Deployment of SigNoz on a $5 VPS with Automatic TLS and Alertmanager Integration

Jun 17, 2026 · Mahbubur Riad

Deploy SigNoz on a $5 VPS with Docker Compose, auto‑TLS via Caddy, Alertmanager alerts, and OpenTelemetry integration – a cheap, self‑hosted observability stack.

On this page

Introduction

Self‑hosted observability used to be an “enterprise‑only” hobby. Today a $5 virtual private server (VPS) can run a full‑featured APM stack that collects logs, traces, and metrics from any language that supports OpenTelemetry. In this tutorial we’ll spin up SigNoz using Docker Compose, secure it automatically with Caddy (TLS via Let’s Encrypt), and hook Alertmanager into the mix so you get real‑time alerts when something goes wrong.

Primary keyword: SigNoz Docker Compose setup
Secondary keywords: self‑hosted observability, OpenTelemetry integration, automatic TLS with Caddy, Alertmanager alerts for SigNoz, low‑cost VPS monitoring

The guide assumes you have a fresh Ubuntu 22.04 VPS with at least 1 GB RAM (the $5 tier on most providers). All commands are run as a non‑root user with sudo privileges.


1. Prerequisites

Item Minimum Why it matters
CPU 1 vCPU SigNoz processes data in real time; a single core is enough for low traffic.
RAM 1 GB Docker containers (PostgreSQL, ClickHouse, SigNoz) comfortably fit; add swap if needed.
Disk 20 GB SSD ClickHouse stores time‑series data; SSD speeds up queries.
OS Ubuntu 22.04 LTS (or Debian 11) Official Docker packages are built for these distros.
Domain A fully qualified domain name (FQDN) pointing to your VPS IP Required for Let’s Encrypt TLS.
Ports 80, 443, 3000 (optional) 80/443 for Caddy, 3000 for SigNoz UI (proxied).

Tip: If your provider only gives 512 MB RAM, enable a 1 GB swap file before proceeding.

Bash
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

2. Install Docker Engine & Docker Compose

Docker Engine provides the container runtime, while Docker Compose orchestrates the multi‑container stack.

Bash
# Add Docker’s official GPG key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg

# Set up the stable repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
  https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io

# Verify installation
docker run --rm hello-world

Install Docker Compose (v2 plugin) which ships with Docker Engine on Ubuntu 22.04, but we’ll ensure the latest version.

Bash
DOCKER_COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep tag_name | cut -d '"' -f4)
sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

# Test
docker compose version

Add your user to the docker group so you don’t need sudo for every command:

Bash
sudo usermod -aG docker $USER
newgrp docker

3. Create a Directory Structure

Bash
mkdir -p ~/signoz/{caddy,alertmanager}
cd ~/signoz

The layout:

Text
signoz/
├─ docker-compose.yml
├─ caddy/
│  └─ Caddyfile
└─ alertmanager/
   └─ alertmanager.yml

4. Docker Compose File for SigNoz

SigNoz’s official Docker Compose (as of the latest GitHub release) includes ClickHouse, PostgreSQL, Redis, Kafka, and the SigNoz UI/API. We’ll add Caddy and Alertmanager as separate services.

YAML
# docker-compose.yml
version: "3.8"

services:
  # Core observability stack
  clickhouse:
    image: clickhouse/clickhouse-server:23.9
    restart: unless-stopped
    volumes:
      - clickhouse-data:/var/lib/clickhouse
    ulimits:
      nofile:
        soft: 262144
        hard: 262144

  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: signoz
      POSTGRES_PASSWORD: signoz
      POSTGRES_DB: signoz
    volumes:
      - postgres-data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]

  kafka:
    image: bitnami/kafka:3.5
    restart: unless-stopped
    environment:
      KAFKA_CFG_ZOOKEEPER_CONNECT: zookeeper:2181
      KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
      ALLOW_PLAINTEXT_LISTENER: "yes"
    depends_on:
      - zookeeper

  zookeeper:
    image: bitnami/zookeeper:3.8
    restart: unless-stopped
    environment:
      ALLOW_ANONYMOUS_LOGIN: "yes"

  signoz-frontend:
    image: signoz/signoz-frontend:latest
    restart: unless-stopped
    environment:
      VITE_APP_BACKEND_URL: http://signoz:8080
    depends_on:
      - signoz

  signoz:
    image: signoz/signoz:latest
    restart: unless-stopped
    environment:
      # Database connections
      POSTGRES_HOST: postgres
      POSTGRES_PORT: 5432
      POSTGRES_USER: signoz
      POSTGRES_PASSWORD: signoz
      POSTGRES_DB: signoz
      CLICKHOUSE_HOST: clickhouse
      CLICKHOUSE_PORT: 9000
      # Kafka
      KAFKA_BOOTSTRAP_SERVERS: kafka:9092
      # Alertmanager webhook URL (set later)
      ALERTMANAGER_WEBHOOK_URL: http://alertmanager:9093/api/v2/alerts
    ports:
      - "8080:8080"   # API port (proxied later)
    depends_on:
      - clickhouse
      - postgres
      - kafka

  # Caddy – automatic TLS & reverse proxy
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - signoz

  # Alertmanager – receives alerts from SigNoz
  alertmanager:
    image: prom/alertmanager:v0.27.0
    restart: unless-stopped
    volumes:
      - ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml
    ports:
      - "9093:9093"

volumes:
  clickhouse-data:
  postgres-data:
  caddy_data:
  caddy_config:

Explanation of key parts

  • Caddy runs on ports 80/443, handles Let’s Encrypt TLS automatically, and proxies https://yourdomain.com to the SigNoz UI (signoz-frontend:3000 internally).
  • Alertmanager is exposed on port 9093 for the web UI and receives alerts via a webhook defined in the signoz service (ALERTMANAGER_WEBHOOK_URL).
  • The stack uses ClickHouse for high‑performance time‑series storage, PostgreSQL for metadata, and Kafka for ingesting OpenTelemetry spans.

5. Configure Caddy for Automatic TLS

Create caddy/Caddyfile (replace example.com with your domain).

CADDY
# Caddyfile
example.com {
    encode gzip
    reverse_proxy signoz-frontend:3000
    # Optional: health endpoint
    health_path /health
}

Caddy will:

  1. Listen on port 80, obtain a Let’s Encrypt certificate for example.com.
  2. Redirect HTTP → HTTPS.
  3. Serve the SigNoz UI on https://example.com.

Note: If you need sub‑domains (e.g., metrics.example.com), add another block with its own reverse_proxy.


6. Set Up Alertmanager

Create a minimal alertmanager/alertmanager.yml. This config sends alerts to a Slack channel; replace the webhook URL with your own.

YAML
# alertmanager.yml
global:
  resolve_timeout: 5m

route:
  receiver: 'slack-notifications'
  group_by: ['alertname']
  group_wait: 30s
  group_interval: 5m
  repeat_interval: 1h

receivers:
  - name: 'slack-notifications'
    slack_configs:
      - api_url: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX'
        channel: '#observability-alerts'
        send_resolved: true

You can add other receivers (PagerDuty, email) later – Alertmanager’s syntax is the same as in the Prometheus ecosystem.


7. Launch the Stack

Bash
docker compose up -d

Docker will pull ~1 GB of images and start all containers. Verify everything is up:

Bash
docker compose ps
# Expected: all services listed as "Up"

Give Caddy a minute to finish the TLS handshake. Then open your browser:

Text
https://example.com

You should see the SigNoz login screen (default credentials: [email protected] / admin). Change the password immediately via Settings → Account.


8. Verify ClickHouse Health

Bash
docker exec -it signoz_clickhouse_1 clickhouse-client -q "SELECT count() FROM system.tables"

A non‑zero count confirms ClickHouse is functional.


9. Connect Your Applications with OpenTelemetry

SigNoz speaks native OpenTelemetry. Below are quick snippets for three popular runtimes.

9.1 Node.js (JavaScript)

Bash
npm install @opentelemetry/api @opentelemetry/sdk-node \
            @opentelemetry/auto-instrumentations-node \
            @opentelemetry/exporter-trace-otlp-http
JavaScript
// otel-setup.js
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http');

const traceExporter = new OTLPTraceExporter({
  url: 'http://example.com/v1/traces', // Caddy proxies to SigNoz
});

const sdk = new NodeSDK({
  traceExporter,
  instrumentations: [getNodeAutoInstrumentations()],
});

sdk.start()
  .then(() => console.log('OpenTelemetry initialized'))
  .catch(console.error);

Add require('./otel-setup') at the entry point of your app.

9.2 Python

Bash
pip install opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation
PYTHON
# otel_setup.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.auto_instrumentation import AutoInstrumentation

trace.set_tracer_provider(TracerProvider())
otlp_exporter = OTLPSpanExporter(endpoint="http://example.com/v1/traces")
span_processor = BatchSpanProcessor(otlp_exporter)
trace.get_tracer_provider().add_span_processor(span_processor)

# Auto‑instrument popular libraries (Flask, Django, requests, etc.)
AutoInstrumentation().instrument()

Import otel_setup before your Flask/Django app starts.

9.3 Go

Bash
go get go.opentelemetry.io/otel/sdk@latest
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@latest
GO
// otel.go
package main

import (
    "go.opentelemetry.io/otel"
    "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
    "go.opentelemetry.io/otel/sdk/trace"
    "log"
)

func initTracer() {
    exporter, err := otlptracehttp.New(context.Background(),
        otlptracehttp.WithEndpoint("example.com/v1/traces"),
        otlptracehttp.WithInsecure(), // Caddy handles TLS
    )
    if err != nil {
        log.Fatalf("failed to create exporter: %v", err)
    }
    tp := trace.NewTracerProvider(trace.WithBatcher(exporter))
    otel.SetTracerProvider(tp)
}

Call initTracer() early in main().

Result: All three services will push spans, logs, and metrics to SigNoz, where you can query them from the UI or set up alerts.


10. Create an Alert in SigNoz

  1. Log into the SigNoz UI.
  2. Navigate to Alerts → New Alert.
  3. Choose a metric (e.g., http_request_duration_seconds > 2s).
  4. Set Severity to Critical and Notification Channel to Alertmanager.
  5. Save.

When the condition fires, SigNoz sends a POST request to http://alertmanager:9093/api/v2/alerts. Alertmanager then routes it to Slack (or any other configured receiver).


11. Pros & Cons of the Stack

Aspect Advantages Drawbacks
Cost Runs on a $5 VPS; all components are free and open source. Limited to low traffic; heavy workloads may need a bigger machine.
Observability Unified UI for logs, traces, metrics; native OpenTelemetry support. Still maturing – some advanced features (e.g., distributed profiling) are missing.
TLS Management Caddy auto‑renews Let’s Encrypt certs, no manual steps. Caddy adds another container to manage; misconfiguration can block traffic.
Alerting Alertmanager integration gives flexible routing (Slack, PagerDuty, email). Requires you to maintain Alertmanager config; no built‑in UI for complex routing.
Self‑hosted Full data ownership; no vendor lock‑in. You’re responsible for backups, updates, and security patches.
Scalability ClickHouse handles high ingest rates. Horizontal scaling across multiple VPSes is non‑trivial; not a “cloud‑native” service mesh.

12. Who Should Use This?

  • Start‑ups or hobby projects that need production‑grade observability without SaaS costs.
  • DevOps engineers who prefer data‑privacy and want to keep telemetry in‑house.
  • Self‑hosters already comfortable with Docker Compose and basic Linux administration.
  • Teams experimenting with OpenTelemetry – the stack provides a ready‑made backend for all languages.

13. When Not to Use This?

  • High‑traffic SaaS services that ingest millions of spans per second – a single $5 VPS will saturate quickly.
  • Compliance‑heavy environments that require certified logging solutions (e.g., PCI‑DSS) – you may need a hardened, audited stack.
  • Teams lacking Docker/Kubernetes expertise – the learning curve for managing multiple containers and TLS can be steep.
  • Scenarios needing built‑in AI‑driven anomaly detection – SigNoz currently offers only rule‑based alerts.

14. Backup & Maintenance Tips

  1. Database backups – schedule a daily dump of PostgreSQL and ClickHouse.
    Bash
    docker exec -t signoz_postgres_1 pg_dump -U signoz signoz > ~/backup/signoz_pg_$(date +%F).sql
    docker exec -t signoz_clickhouse_1 clickhouse-client --query "BACKUP DATABASE default TO '/var/lib/clickhouse/backup_$(date +%F)'" 
    
  2. Image updates – run docker compose pull && docker compose up -d weekly.
  3. Renewal check – Caddy logs will display certificate renewal; monitor /var/log/caddy for errors.
  4. Log rotation – configure Docker’s log-opt max-size=10m if logs grow fast.

15. Frequently Asked Questions

Question Answer
Do I need a domain for TLS? Yes. Let’s Encrypt requires a publicly resolvable FQDN. If you only need HTTP, you can skip Caddy and expose SigNoz directly (not recommended for production).
Can I run the stack on Docker Swarm or Kubernetes? Absolutely. The official SigNoz repo includes Helm charts. The Docker Compose file can be converted with docker compose convert.
How much RAM does ClickHouse actually use? ClickHouse is memory‑efficient, but it keeps hot columns in RAM. For a low‑traffic setup 1 GB is enough; monitor RSS in docker stats.
What if Let’s Encrypt rate limits me? Caddy respects the ACME rate limits. If you repeatedly fail validation, wait an hour before retrying.
Is there a UI for managing Alertmanager routes? Not natively. You can use the open‑source Alertmanager UI (e.g., prom/alertmanager's built‑in UI) or third‑party tools like amtool for CLI management.

16. Wrap‑Up

Deploying a full observability stack on a $5 VPS is no longer a myth. By leveraging SigNoz, Docker Compose, Caddy for automatic TLS, and Alertmanager for alert routing, you get a production‑ready APM solution that respects privacy and budget constraints. The OpenTelemetry snippets show how painless it is to instrument any modern application, and the alerting workflow demonstrates end‑to‑end visibility.

Give it a spin, tweak the alert rules, and you’ll have a monitoring foundation that scales with your service—until you outgrow the $5 tier, at which point a move to a larger VM or a managed service is straightforward.

Happy monitoring!

For more hands‑on guides, visit mahbuburriad.com.


Related

Related posts