Self-hosted observability stack — ingest, store and explore your data at scale
One command. Full observability stack. No cloud required.
XD-oss-stack is a self-hosted, Docker-based observability platform built for teams who want full control over their telemetry data — no vendor lock-in, no per-seat pricing, no data leaving your infrastructure.
| Ingest | Receive logs from any service via OpenTelemetry (OTLP/HTTP) |
| Store | Apache Doris — a high-performance analytical database built for logs at scale |
| Explore | Web UI with full-text search, filtering, and real-time querying |
| Deploy | 4 Docker containers, one install command, runs anywhere |
flowchart LR
subgraph SOURCES["Upstream — Sources"]
SDK["OTel SDKs\nPython · Go · Node · Java · .NET"]
GEN["telemetrygen\nTest data generator"]
HTTP["HTTP / REST\nDirect OTLP push"]
end
subgraph ENGINE["XD-oss-stack"]
direction TB
COL["OTel Collector\nReceives · Batches · Exports"]
subgraph DORIS["Apache Doris v4.0.4"]
FE["Doris FE\nQuery layer · :9030"]
BE["Doris BE\nColumnar storage · :8040"]
FE <-->|internal| BE
end
APP["XD-APP + XD-API\n:80"]
COL -->|Stream Load| FE
FE -->|SQL| APP
end
subgraph CONSUMERS["Downstream"]
DB1[("otel_db\nlogs · traces · metrics")]
DB2[("demo_otel_db\n10,000 demo records")]
UI["Web UI\nhttp://your-ip"]
end
SDK -->|OTLP/HTTP :4318| COL
GEN -->|OTLP/HTTP :4318| COL
HTTP -->|OTLP/HTTP :4318| COL
BE --> DB1
BE --> DB2
FE -->|Query| UI
APP --> UI
| Database | Tables | Purpose |
|---|---|---|
otel_db |
otel_logs, otel_traces, otel_metrics |
Live telemetry from OTel Collector |
demo_otel_db |
otel_logs |
Pre-seeded demo data (10,000 records) |
| Component | Minimum | Recommended |
|---|---|---|
| CPU | 2 cores | 4+ cores |
| RAM | 4 GB | 8 GB |
| Disk | 20 GB | 100 GB |
| Docker | 20.10+ | latest |
| OS | Ubuntu 20.04+ / macOS 12+ | Ubuntu 22.04 |
Low RAM? The installer automatically detects available memory and applies reduced JVM settings for machines with less than 8 GB RAM.
Note Apache Doris BE requires AVX2 processor support. Some virtualization platforms (e.g. ProxMox) may not expose AVX2 instructions to guest VMs — Doris BE will fail to start on these environments.
| OS | Requirements |
|---|---|
| Linux | Nothing — Docker auto-installed if missing |
| macOS | Docker Desktop running |
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)" ╔═══════════════════════════════════════════════════════╗
║ XD-oss-stack Installer v1.0.0 ║
╚═══════════════════════════════════════════════════════╝
▶ Checking system requirements
✓ CPU: 8 cores ✓ RAM: 16 GB
✓ Disk: 120 GB free ✓ Ports: 80, 4318 available
▶ Pulling Docker images
✓ All images downloaded
▶ Starting containers
ℹ This may take up to 10 minutes on first run while Doris initializes.
✓ All containers started
▶ Waiting for services
✓ Doris Frontend healthy (67s)
✓ Doris Backend healthy (102s)
✓ Application healthy (142s)
✓ Login endpoint ready (198s)
✓ OTel Collector running
╔═══════════════════════════════════════════════════════╗
║ ✓ Installation Complete! ║
║ Happy Xpluring your data! ║
╚═══════════════════════════════════════════════════════╝
After install (~3-5 minutes on first boot):
| Service | URL | Credentials |
|---|---|---|
| Web UI | http://<your-ip> |
admin / admin |
| OTLP HTTP | http://<your-ip>:4318 |
— |
docker run --rm \
--network xd-oss-stack_otel-net \
ghcr.io/open-telemetry/opentelemetry-collector-contrib/telemetrygen:latest \
logs \
--otlp-endpoint otel-collector:4318 \
--otlp-http \
--otlp-insecure \
--duration 10s \
--rate 100 \
--service my-servicePython
pip install opentelemetry-sdk opentelemetry-exporter-otlp-proto-httpimport logging
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
from opentelemetry.sdk.resources import Resource
resource = Resource.create({"service.name": "my-service"})
provider = LoggerProvider(resource=resource)
provider.add_log_record_processor(
BatchLogRecordProcessor(
OTLPLogExporter(endpoint="http://<YOUR_HOST>:4318/v1/logs")
)
)
set_logger_provider(provider)
logger = logging.getLogger("my-service")
logger.addHandler(LoggingHandler(logger_provider=provider))
logger.setLevel(logging.INFO)
logger.info("Hello from my-service!")Node.js
npm install @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/sdk-nodeconst { LoggerProvider, BatchLogRecordProcessor } = require('@opentelemetry/sdk-logs');
const { OTLPLogExporter } = require('@opentelemetry/exporter-logs-otlp-http');
const provider = new LoggerProvider();
provider.addLogRecordProcessor(
new BatchLogRecordProcessor(
new OTLPLogExporter({ url: 'http://<YOUR_HOST>:4318/v1/logs' })
)
);
const logger = provider.getLogger('my-service');
logger.emit({ severityText: 'INFO', body: 'Hello from my-service!' });Java
# Download OpenTelemetry Java Agent
wget https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
# Run your app with auto-instrumentation
java \
-javaagent:opentelemetry-javaagent.jar \
-Dotel.service.name=my-service \
-Dotel.logs.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://<YOUR_HOST>:4318 \
-jar app.jar// Or manual SDK
import io.opentelemetry.api.logs.Logger;
// point exporter to http://<YOUR_HOST>:4318/v1/logs
logger.logRecordBuilder()
.setBody("Hello from my-service!")
.setSeverity(Severity.INFO)
.emit();Go
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttppackage main
import (
"context"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/log/global"
sdklog "go.opentelemetry.io/otel/sdk/log"
)
func main() {
exporter, _ := otlploghttp.New(context.Background(),
otlploghttp.WithEndpoint("<YOUR_HOST>:4318"),
otlploghttp.WithInsecure(),
)
provider := sdklog.NewLoggerProvider(
sdklog.WithProcessor(sdklog.NewBatchProcessor(exporter)),
)
global.SetLoggerProvider(provider)
}.NET
dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol
dotnet add package OpenTelemetry.Extensions.Hostingusing OpenTelemetry.Logs;
builder.Logging.AddOpenTelemetry(options => {
options.AddOtlpExporter(otlp => {
otlp.Endpoint = new Uri("http://<YOUR_HOST>:4318/v1/logs");
});
});
var logger = app.Services.GetRequiredService<ILogger<Program>>();
logger.LogInformation("Hello from my-service!");Ruby
gem install opentelemetry-sdk opentelemetry-exporter-otlprequire 'opentelemetry-sdk'
require 'opentelemetry-exporter-otlp'
OpenTelemetry::SDK.configure do |c|
c.service_name = 'my-service'
c.add_span_processor(
OpenTelemetry::SDK::Trace::Export::BatchSpanProcessor.new(
OpenTelemetry::Exporter::OTLP::Exporter.new(
endpoint: 'http://<YOUR_HOST>:4318/v1/logs'
)
)
)
endPHP
composer require open-telemetry/sdk open-telemetry/exporter-otlp<?php
use OpenTelemetry\SDK\Logs\LoggerProvider;
use OpenTelemetry\Contrib\Otlp\OtlpHttpTransportFactory;
use OpenTelemetry\Contrib\Otlp\LogsExporter;
$transport = (new OtlpHttpTransportFactory())->create(
'http://<YOUR_HOST>:4318/v1/logs', 'application/x-protobuf'
);
$exporter = new LogsExporter($transport);
$provider = LoggerProvider::builder()
->addLogRecordProcessor(new SimpleLogRecordProcessor($exporter))
->build();
$logger = $provider->getLogger('my-service');
$logger->logRecord()->setBody('Hello from my-service!')->emit();Rust
# Cargo.toml
[dependencies]
opentelemetry = "0.22"
opentelemetry-otlp = { version = "0.15", features = ["logs"] }
opentelemetry_sdk = { version = "0.22", features = ["logs"] }use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::logs::LoggerProvider;
let exporter = opentelemetry_otlp::new_exporter()
.http()
.with_endpoint("http://<YOUR_HOST>:4318/v1/logs");
let provider = LoggerProvider::builder()
.with_batch_exporter(exporter.build_log_exporter().unwrap(),
opentelemetry_sdk::runtime::Tokio)
.build();~/xd-oss-stack/manage.sh status # container status
~/xd-oss-stack/manage.sh logs # follow app logs
~/xd-oss-stack/manage.sh logs otel-collector
~/xd-oss-stack/manage.sh logs doris-fe
~/xd-oss-stack/manage.sh logs doris-be
~/xd-oss-stack/manage.sh update # pull latest images
~/xd-oss-stack/manage.sh restart # restart all containers
~/xd-oss-stack/manage.sh stop # stop all containers (preserves data)
~/xd-oss-stack/manage.sh uninstall # remove everythingbash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)"When prompted "Clean existing data for fresh install?" — select n to keep your existing data:
Important: Selecting
npreserves all your Doris data (logs, traces, metrics) and app data (user accounts, settings). The installer will migrate data to the new location if needed.
On first boot the stack seeds 10,000 realistic log records across 5 microservices so you can explore immediately:
| Service | Records | Severity |
|---|---|---|
| api-gateway | ~2,000 | 70% INFO · 15% WARN · 15% ERROR |
| auth-service | ~2,000 | 70% INFO · 15% WARN · 15% ERROR |
| payment-service | ~2,000 | 70% INFO · 15% WARN · 15% ERROR |
| notification-service | ~2,000 | 70% INFO · 15% WARN · 15% ERROR |
| inventory-service | ~2,000 | 70% INFO · 15% WARN · 15% ERROR |
Install stopped midway — how to clean up and retry
If the installer stops after "Writing configuration" for any reason, clean up before retrying:
docker compose -f ~/xd-oss-stack/docker-compose.yml down -v 2>/dev/null || true
sudo rm -rf /var/lib/xd-oss-stack
docker network prune -f
# Then re-run the installer
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)"Stack not starting
docker logs otel-doris-fe
docker logs otel-doris-beDoris FE fails with "insufficient memory"
Your machine has less than 8 GB RAM. The installer automatically applies reduced JVM settings. If it still fails:
free -h # check available memory
~/xd-oss-stack/manage.sh stop
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)"No data in otel_db.otel_logs
docker logs otel-collectorApp not ready after 5 minutes
docker logs -f otel-appPort 80 or 4318 already in use
sudo lsof -i :80
sudo lsof -i :4318macOS: process.lock AccessDeniedException
The installer uses ~/.xd-oss-stack/data on macOS to avoid bind mount permission issues. If it still fails:
rm -rf ~/.xd-oss-stack
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)"Full reset (wipes all data)
~/xd-oss-stack/manage.sh uninstall
bash -c "$(curl -fsSL https://raw.githubusercontent.com/xplurdata/oss-stack/main/install.sh)"| xplurdata | |
| 💬 Slack | Join our Slack |
| 🌐 Website | xplurdata.com |
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).
Built by Xplurdata · Happy Xpluring your data!
Apache Doris · OpenTelemetry · xplurdata.com
⭐ Star this repo if you find it useful!
