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.
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.
# 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.
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:
sudo usermod -aG docker $USER
newgrp docker
3. Create a Directory Structure
mkdir -p ~/signoz/{caddy,alertmanager}
cd ~/signoz
The layout:
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.
# 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.comto the SigNoz UI (signoz-frontend:3000internally). - Alertmanager is exposed on port 9093 for the web UI and receives alerts via a webhook defined in the
signozservice (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).
# Caddyfile
example.com {
encode gzip
reverse_proxy signoz-frontend:3000
# Optional: health endpoint
health_path /health
}
Caddy will:
- Listen on port 80, obtain a Let’s Encrypt certificate for
example.com. - Redirect HTTP → HTTPS.
- Serve the SigNoz UI on
https://example.com.
Note: If you need sub‑domains (e.g.,
metrics.example.com), add another block with its ownreverse_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.
# 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
docker compose up -d
Docker will pull ~1 GB of images and start all containers. Verify everything is up:
docker compose ps
# Expected: all services listed as "Up"
Give Caddy a minute to finish the TLS handshake. Then open your browser:
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
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)
npm install @opentelemetry/api @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http
// 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
pip install opentelemetry-sdk opentelemetry-exporter-otlp opentelemetry-instrumentation
# 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
go get go.opentelemetry.io/otel/sdk@latest
go get go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp@latest
// 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
- Log into the SigNoz UI.
- Navigate to Alerts → New Alert.
- Choose a metric (e.g.,
http_request_duration_seconds> 2s). - Set Severity to Critical and Notification Channel to Alertmanager.
- 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
- 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)'" - Image updates – run
docker compose pull && docker compose up -dweekly. - Renewal check – Caddy logs will display certificate renewal; monitor
/var/log/caddyfor errors. - Log rotation – configure Docker’s
log-opt max-size=10mif 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.