Skip to content
Go back

Self-Host SigNoz: Install Guide

By SumGuy 18 min read
Self-Host SigNoz: Install Guide
Contents

Why Self-Host SigNoz Instead of Just Paying Datadog

Datadog is great. It’s also priced like you’re running a FAANG data center. If you’ve ever gotten a bill mid-month and done a double-take, you already know why we’re here.

SigNoz is a full-stack observability platform (traces, metrics, logs, dashboards, alerts) built on OpenTelemetry and ClickHouse. It’s open source, it runs on your hardware, and it doesn’t charge you per seat or per GB of logs like it’s printing money.

The install story changed recently, and if you’re following an older guide (including the first version of this one), you’re going to have a bad time. SigNoz now installs through Foundry, a CLI that generates your deployment from a single YAML file. The guide below is short: install foundryctl, write about seven lines of YAML, run one command, get telemetry.

If you’re still deciding whether SigNoz is the right tool, check out SigNoz vs Uptrace or SigNoz vs Grafana LGTM first. If you know you want SigNoz and you just want the thing running, keep reading.

Full example: Clone the working files at github.com/KingPin/sumguy-examples/observability/self-host-signoz

Also worth reading alongside this guide: OpenTelemetry for Self-Hosters and ClickHouse for Self-Hosted Observability for deeper context on the underlying pieces.


What Changed: install.sh and the Bundled Compose Files Are Dead

As of SigNoz v0.130.0, the install.sh script and the Docker Compose manifests that used to live in deploy/ are deprecated and no longer distributed. If you go looking for signoz/deploy/docker/docker-compose.yaml in the repo today, you’ll find a README pointing you at Foundry and a migration guide. That’s it.

SigNoz’s reasoning is the boring, correct kind: they were maintaining a separate install surface for every platform they supported (Compose, Swarm, Kubernetes manifests, Helm, ECS, and a pile of one-click PaaS targets), and every application release meant updating all of them without getting any wrong. Foundry collapses that into one code path. You declare a target, it generates the manifests.

The vocabulary is aggressively themed, so let’s get it out of the way:

Foundry termWhat it actually is
CastingYour casting.yaml, the one config file that describes the deployment
MoldingsThe components (ClickHouse, Postgres, OTel Collector, SigNoz itself)
PoursThe generated output files, written to pours/
gaugePreflight check that the tools you need are present
forgeGenerate the manifests, touch nothing running
castgauge, then forge, then actually deploy

If you’ve used Terraform, this will feel familiar. forge is your plan, cast is your apply, and the generated files in pours/ are disposable artifacts you regenerate rather than hand-edit.

The architecture also shifted underneath the install change. The old four-container setup (ClickHouse, collector, SigNoz, and a standalone Alertmanager) is now five moving parts, two of which are new and one of which is gone:

The metastore is the one that trips people up on upgrade. Older SigNoz kept metadata in SQLite inside the query service; Postgres is now the default. There’s also a one-shot telemetrystore-migrator container that runs ClickHouse schema migrations and exits, so seeing it in Exited (0) state is correct behaviour, not a crash.

Oh, and the UI moved. It’s on port 8080 now, not 3301.


Prerequisites

Before you spin anything up, make sure your host can handle it. SigNoz is not a lightweight install; ClickHouse is doing real work behind the scenes.

Minimum sizing:

Check your versions:

Terminal window
docker --version
docker compose version

If docker compose version fails, you’re on the old standalone binary. On Ubuntu:

Terminal window
sudo apt install docker-compose-plugin

On Windows, run this inside WSL 2 with Docker Engine installed natively in the distro, not Docker Desktop. ClickHouse Keeper segfaults (exit code 139, restart loop) under Docker Desktop’s virtualization layer on Windows. SigNoz documents this one themselves.


Step 1: Install foundryctl

One line, and it does the sensible things: detects your OS and architecture, verifies the download checksum, and drops the binary in $XDG_BIN_HOME or ~/.local/bin.

Terminal window
curl -fsSL https://signoz.io/foundry.sh | bash

If piping a remote script into bash makes you twitch (fair), the releases page has plain tarballs. Unpack, move foundry_*/bin/foundryctl onto your PATH, done.

Pin a version if you want reproducible installs across machines: curl -fsSL https://signoz.io/foundry.sh | FOUNDRY_VERSION=v0.2.17 bash.

Confirm it works with foundryctl --help. If that comes back “command not found”, ~/.local/bin isn’t on your PATH. Add export PATH="$HOME/.local/bin:$PATH" to your shell config and reload.


Step 2: Write Your casting.yaml

This is what replaces the 200-line Compose file the old version of this guide handed you:

Terminal window
mkdir -p ~/signoz && cd ~/signoz
casting.yaml
apiVersion: v1alpha1
kind: Installation
metadata:
name: signoz
spec:
deployment:
flavor: compose
mode: docker

Seven lines. That’s a complete, working, single-node SigNoz deployment with defaults for every component.

mode and flavor are the pair that picks your target, and they only work in specific combinations:

Targetmodeflavorplatform
Docker Composedockercompose-
Docker Swarmdockerswarm-
Systemd (binary)systemdbinary-
Kuberneteskuberneteskustomize or helm-
AWS ECS on EC2ec2terraformecs

Render, Railway, and Coolify are also supported through platform with their own flavors. You can’t mix values across rows: mode: docker with flavor: helm isn’t a thing, and Foundry says so rather than generating nonsense.

Skip the memorisation. foundryctl gen examples writes working example castings for every supported mode, so copy the one you want.


Step 3: Cast It

Terminal window
foundryctl cast -f casting.yaml

That runs all three stages: gauge checks your Docker install, forge renders the Compose files into pours/deployment/ and writes a casting.yaml.lock, then cast brings the stack up.

If you’re the sort who reads the diff before applying it (you should be), split it:

Terminal window
foundryctl gauge -f casting.yaml # validate prerequisites
foundryctl forge -f casting.yaml # generate files, change nothing running
cd pours/deployment && docker compose up -d

The generated tree looks like this:

pours/deployment/
compose.yaml
ingester/
ingester.yaml
opamp.yaml
telemetrykeeper/
clickhousekeeper/
keeper-0.yaml
telemetrystore/
clickhouse/
config-0-0.yaml
functions.yaml

Do not hand-edit anything in pours/. The next forge overwrites it. Everything you want to change belongs in casting.yaml, which is the whole point of the exercise and also the thing everyone gets wrong the first time.

Check what came up:

Terminal window
docker ps

You should see signoz-signoz-0 on 0.0.0.0:8080, signoz-ingester-1 on 4317/4318, signoz-telemetrystore-clickhouse-0-0, signoz-telemetrykeeper-clickhousekeeper-0, and signoz-metastore-postgres-0. The signoz-telemetrystore-migrator and signoz-telemetrystore-clickhouse-user-scripts containers run once and exit. That’s correct.

Give it 60 to 90 seconds on first boot. ClickHouse builds its schema, the migrator runs, and the other services will restart-loop until dependencies report healthy. Normal, not broken.


Step 4: First Login

Open http://your-host-ip:8080.

You’ll land on the setup wizard asking you to create an admin account. Fill in a name, email, and password. This is local auth stored in the Postgres metastore; nothing phones home.

After login you’ll see the main dashboard. It’s empty. That’s expected, you haven’t sent it any data yet.

Confirm the ingester is actually listening before you go instrumenting things:

Terminal window
curl -v http://localhost:4318/v1/traces
# Expect 400 or 405. The endpoint exists and rejected your empty GET. Good.

Step 5: Wire Up a Real App via OTLP

This part didn’t change, which is the nice thing about OpenTelemetry being a standard rather than a vendor feature. Your apps point at localhost:4317 (gRPC) or localhost:4318 (HTTP) and don’t care what’s behind them.

Python / Flask

OpenTelemetry has an auto-instrumentation path for Python that covers Flask, SQLAlchemy, requests, and a pile of other libraries without you touching application code.

Terminal window
pip install flask \
opentelemetry-distro \
opentelemetry-exporter-otlp-proto-grpc
opentelemetry-bootstrap -a install
app.py
from flask import Flask, jsonify
import time, random
app = Flask(__name__)
@app.route("/")
def index():
# Simulate variable latency so you have something interesting to alert on
time.sleep(random.uniform(0.01, 0.5))
return jsonify({"status": "ok"})
@app.route("/slow")
def slow():
time.sleep(random.uniform(0.8, 2.5))
return jsonify({"status": "done, eventually"})
@app.route("/error")
def error():
raise ValueError("This error is intentional. Probably.")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=False)

Run it with auto-instrumentation pointing at SigNoz:

Terminal window
export OTEL_RESOURCE_ATTRIBUTES="service.name=flask-demo"
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
opentelemetry-instrument python app.py

Hit the endpoints a few times to generate traces:

Terminal window
curl http://localhost:5000/
curl http://localhost:5000/slow
curl http://localhost:5000/error || true

In 15 to 30 seconds flask-demo should appear in the SigNoz Services list.

Node.js

Install deps in your project:

Terminal window
npm install @opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-grpc \
@opentelemetry/exporter-metrics-otlp-grpc

Create a tracing bootstrap file. This must be required before anything else:

tracing.js
const { NodeSDK } = require("@opentelemetry/sdk-node");
const { getNodeAutoInstrumentations } = require("@opentelemetry/auto-instrumentations-node");
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-grpc");
const { OTLPMetricExporter } = require("@opentelemetry/exporter-metrics-otlp-grpc");
const { PeriodicExportingMetricReader } = require("@opentelemetry/sdk-metrics");
const sdk = new NodeSDK({
serviceName: "node-demo",
traceExporter: new OTLPTraceExporter({
url: "grpc://localhost:4317",
}),
metricReader: new PeriodicExportingMetricReader({
exporter: new OTLPMetricExporter({
url: "grpc://localhost:4317",
}),
exportIntervalMillis: 15000,
}),
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
process.on("SIGTERM", () => {
sdk.shutdown().then(() => process.exit(0));
});

Start your app with the tracing file loaded first:

Terminal window
node -r ./tracing.js server.js

Both services will show up in SigNoz within a minute of generating traffic. Traces, spans, service maps, latency distributions, the whole deal.


Step 6: Retention and TTL

Foundry ships sane defaults now, which is a change worth knowing about: 7 days for logs and traces, 30 days for metrics. The old “keep everything until the disk fills” behaviour is gone, so if you were expecting three weeks of trace history, adjust before you need it rather than after.

In the SigNoz UI:

  1. Go to Settings → General
  2. Set your TTL for Traces, Metrics, and Logs independently
  3. Save

SigNoz translates that into ALTER TABLE ... MODIFY TTL statements against ClickHouse.

The caveat about ClickHouse TTL: data doesn’t vanish the instant it expires. ClickHouse deletes expired rows during background merges, which run on their own schedule. In practice data can stick around 1 to 3 days past your TTL. It’s a storage management tool, not a compliance control. Don’t use it as one.

To see what’s actually eating disk:

Terminal window
docker exec -it signoz-telemetrystore-clickhouse-0-0 \
clickhouse-client --query "
SELECT database, table, formatReadableSize(sum(bytes_on_disk)) AS size
FROM system.parts
WHERE active = 1
GROUP BY database, table
ORDER BY sum(bytes_on_disk) DESC
LIMIT 20
"

Note there’s no --user or --password here. Foundry’s generated ClickHouse runs with CLICKHOUSE_SKIP_USER_SETUP=1 and is reachable only on the internal signoz-network, so the default user has no password. That’s fine as long as you don’t publish port 9000 to the host, and Foundry doesn’t.

If a table from that query is the obvious culprit and you want the space back now, force the merge:

Terminal window
docker exec -it signoz-telemetrystore-clickhouse-0-0 \
clickhouse-client --query "OPTIMIZE TABLE <database>.<table> FINAL"

For a homelab, 14 days of traces and 30 days of metrics is a reasonable starting point. Scale up once you know your real volume.


Step 7: Set Up an Alert

An observability platform without alerts is a dashboard you never look at.

Create a metrics-based alert:

  1. Go to Alerts → New Alert Rule
  2. Choose Metrics Based Alert
  3. Pick a metric. signoz_calls_total filtered on 5xx status codes is an easy first one
  4. Set the condition: sum > 5 over 5 minutes
  5. Name it something your 2 AM self will understand, like “High error rate: flask-demo”
  6. Severity Warning

Add a notification channel:

  1. Settings → Alert Channels → New Channel
  2. Pick Slack, webhook, PagerDuty, or email
  3. For Slack, paste the incoming webhook URL
  4. Send the test notification. If it doesn’t arrive, fix that before you trust the rule
  5. Assign the channel to your alert rule

One thing that changed with Foundry: there is no separate Alertmanager container any more. Alert evaluation and routing live inside the signoz service itself. If you’re following an old guide that tells you to edit alertmanager.yml and docker compose restart alertmanager, that container doesn’t exist in your stack. Configure it in the UI instead.


Step 8: Customising Without Fighting Foundry

Everything below follows the same loop: edit casting.yaml, re-run foundryctl cast.

Pinning versions. ClickHouse and the Keeper come out of Foundry already pinned, but signoz and ingester default to latest, which is a fine way to get surprised on a Tuesday. Pin them:

casting.yaml
spec:
deployment:
flavor: compose
mode: docker
signoz:
spec:
image: signoz/signoz:v0.137.1
ingester:
spec:
image: signoz/signoz-otel-collector:v0.137.1
telemetrystore:
spec:
image: clickhouse/clickhouse-server:25.12.5

Swapping the metastore. Postgres is the default and the right call for anything you care about. Set metastore.kind: sqlite if you want the old single-node behaviour with one less container.

Resource limits and anything else Foundry doesn’t model. Foundry splits config into two layers: spec.<component> is the application domain (images, replicas, env, config files), and spec.patches is the platform domain, applied as raw JSON Patch (RFC 6902) against a generated file. Capping ClickHouse memory looks like this:

casting.yaml
spec:
patches:
- target: "deployment/compose.yaml"
operations:
- op: add
path: /services/signoz-telemetrystore-clickhouse-0-0/mem_limit
value: "6g"

Run foundryctl forge once with no patches first so you can read the generated compose.yaml and get the JSON pointer paths right. Guessing them is how you spend an evening debugging a patch that silently targeted nothing.

Upgrading. Bump the image tags in casting.yaml and cast again. One gotcha worth burning into memory: Compose does not restart a container when only the contents of a mounted config file change, so a plain cast can leave you running new configs that nothing has read. Force it:

Terminal window
docker compose -f pours/deployment/compose.yaml up -d --force-recreate

Recreate everything in one command rather than restarting services individually. Restarting only the keeper leaves ClickHouse unable to reconnect until it’s restarted too. Your data is in volumes and isn’t affected.


Migrating an Existing Compose Install

If you already have SigNoz running from install.sh or the old deploy/ Compose files, you can move to Foundry without losing data. The short version:

  1. Keep a copy of your existing docker-compose.yaml and any config it references. SigNoz no longer distributes these, so that copy is your only rollback path.
  2. Install foundryctl.
  3. Write a casting that reproduces the legacy layout (metastore: sqlite, telemetrykeeper: zookeeper) and use spec.patches to rename the generated volumes onto your existing ones (signoz-clickhouse, signoz-zookeeper-1, signoz-sqlite).
  4. foundryctl forge and read the output before you touch anything running.
  5. docker compose down on the old stack. Never pass -v or --volumes, that deletes the data you’re trying to keep.
  6. foundryctl cast.

You’ll also need to carry over your ClickHouse replica and shard macros from the old config.xml into the new casting, and the ClickHouse config moved from XML to YAML in the process. SigNoz’s migration guide has the full castings for both Compose and Swarm, including the user: root patch ZooKeeper needs to read files the legacy stack created. Follow that one rather than improvising; this is the one part of the process where getting creative costs you your trace history.


Reverse Proxy and Backups

Reverse Proxy

Port 8080 on the LAN is fine for internal use. For HTTPS and a real hostname, put Caddy in front:

Caddyfile
signoz.your-domain.com {
reverse_proxy localhost:8080
}

Caddy handles the cert. For Traefik, add labels via a patch on the signoz-signoz-0 service rather than editing the generated Compose file. SigNoz itself needs no special configuration either way.

Backups

Your data lives in Docker volumes, and Foundry names them predictably:

The metastore is small and matters more than people expect. Losing it means rebuilding every dashboard and alert you’ve ever made:

Terminal window
docker exec signoz-metastore-postgres-0 \
pg_dump -U signoz signoz | gzip > signoz-metastore-$(date +%F).sql.gz

For ClickHouse, a cold volume copy with the stack stopped is the simplest thing that actually works:

Terminal window
docker compose -f pours/deployment/compose.yaml down
docker run --rm \
-v signoz-telemetrystore-0-0-data:/data:ro \
-v "$PWD/backups:/backup" \
alpine tar czf /backup/clickhouse-$(date +%F).tar.gz -C /data .
docker compose -f pours/deployment/compose.yaml up -d

Where downtime isn’t acceptable, use clickhouse-backup (the third-party tool) for incremental backups and S3 upload. For a homelab, a weekly snapshot to the NAS plus the Postgres dump covers you.

Back up casting.yaml too. It’s seven lines and it rebuilds your entire deployment, which is the actual selling point here. Put it in git.


Troubleshooting

No data showing up

Check the ingester is reachable from your app host:

Terminal window
curl -v http://signoz-host:4318/v1/traces
nc -zv signoz-host 4317

If the connection is refused, check the ports are bound:

Terminal window
docker compose -f pours/deployment/compose.yaml ps
# Look for 0.0.0.0:4317-4318->4317-4318/tcp on the ingester

Check ingester logs for export errors:

Terminal window
docker compose -f pours/deployment/compose.yaml logs ingester --tail=100 2>&1 | grep -i error

The usual culprit is connection refused to ClickHouse while it’s still coming up.

Ports vanish when you scale the ingester

If you set ingester.spec.cluster.replicas above 1 under Compose, the OTLP ports stop being published to the host entirely. Compose can’t publish a fixed host port for a scaled service, so 4317 and 4318 become reachable only on the internal network. Same applies to the MCP server on 8000. Either run one ingester replica, put your own proxy in front, or move to the swarm or kubernetes casting where replicas get load-balanced properly.

A container is unhealthy or the UI won’t load

Terminal window
docker compose -f pours/deployment/compose.yaml logs -f signoz-signoz-0

If signoz-signoz-0 is healthy but the page won’t load, confirm you’re on 8080 and not 3301 out of muscle memory. Everything published before mid-2026 says 3301.

Containers keep restarting

Almost always memory. SigNoz wants 4 GB allocated to Docker as a floor and is happier with 8. On Docker Desktop, raise the VM memory allocation. On Linux, check dmesg | grep -i oom for the OOM killer’s handiwork.

If ClickHouse specifically is the one dying, cap it explicitly with a patch (see the customisation section above) so it gets rejected queries rather than a hard kill.

The migrator container is “Exited”

That’s the design. signoz-telemetrystore-migrator runs the ClickHouse schema migrations once and stops. Exit code 0 means it worked. Exit code anything-else means read its logs before debugging anything downstream, because nothing above it will work correctly.


The SumGuy Take

SigNoz is still good software: OpenTelemetry-native so your instrumentation isn’t vendor-locked, a UI that’s usable without a training course, and ClickHouse underneath meaning real compression and fast queries on mid-range hardware.

Foundry is a clear improvement over what it replaced, and I say that as someone whose previous advice here was “skip their deploy script and hand-roll your own Compose file.” That made sense when the alternative was cloning a mono-repo to get one YAML file. It doesn’t now. A seven-line declarative config that generates a correct five-component stack, pins its own ClickHouse version, and works the same across Compose, systemd, and Kubernetes beats a Compose file I maintain by hand and get wrong the moment SigNoz adds a Postgres metastore I didn’t know about.

The cost is a layer of indirection and a themed vocabulary. Casting, molding, forge, pour. It’s a lot of metalworking for docker compose up. But the generated files are plain Compose YAML sitting in pours/ where you can read them, and you can stop at forge and run Compose yourself. That’s the right escape hatch to have.

The hardware reality hasn’t changed. ClickHouse is not small, 8 GB is the honest minimum, and running this alongside your app stack will hurt. On a dedicated VM it’s fine.

If you want to understand what your services are actually doing without signing up for a Datadog trial, this is a good afternoon’s work. Just put casting.yaml in git, because it’s now the only file that matters.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
Blackbox Exporter: HTTP/TCP/DNS/ICMP Probes
Next Post
SLO/SLI for Home Lab Services

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts