Ingestion
Linux server agent
One command installs a pinned, checksum-verified OpenTelemetry Collector as a hardened systemd service sending host metrics, journald logs and Docker stats.
Fill this page in with a real API key
The samples below carry a placeholder key and an example host. Log in or create an account to issue one and have them rewritten in place.
Or hand it to a coding agent
Copy this prompt into Claude Code — or any agent that can read a URL — and let it do the setup against the current version of this page.
Set up Bilis — self-hosted log, trace and metric storage — in this project, following its "Linux server agent" guide: https://bilis.app/docs/ingestion/server-agent.md Bilis endpoint: https://bilis.app API key: bilis_YOUR_API_KEY Read the guide first, then make the changes it describes. Keep the API key in the environment rather than in a file that gets committed, and finish by sending one record and confirming it arrives.
The fastest way to watch a Linux box. One command, run as root, installs the
official OpenTelemetry Collector as a systemd service called bilis-agent and
starts sending three things to one Bilis project:
- Host metrics — CPU, memory, load, disks, filesystems, network, paging and process counts.
- System logs — everything journald receives, one
service.nameper systemd unit. - Docker container stats — CPU, memory, network and block I/O per container, when Docker is installed.
curl -fsSL https://your-bilis-host/install.sh | sudo BILIS_API_KEY=bilis_YOUR_API_KEY sh
The app fills this command in for you: the project page shows it with a placeholder, and the dialog that appears when you create an API key shows it with the real key — the only moment the key exists in plaintext. The script checks the key against Bilis before it changes anything on the machine, so a typo costs you nothing.
It is not a home-grown agent. It is the upstream otelcol-contrib binary with a
generated config, so everything below is standard Collector behaviour you can
read, change and debug with the Collector's own documentation. For file-based
logs this agent does not read — fail2ban, UFW, application log files — see the
manual Linux host recipe; the two run side by
side.
Requirements
- Linux with systemd (Debian, Ubuntu, RHEL, Fedora, Amazon Linux, Arch…).
- root — the command runs under
sudo. - amd64 or arm64.
curlorwget,tarandsha256sum.- Outbound HTTPS to your Bilis host. The agent opens no listening ports.
The script refuses early, with a message naming what is missing, if any of these is not met.
What gets installed
| Path | What |
|---|---|
/opt/bilis-agent/otelcol-contrib |
The official Collector release, pinned to v0.159.0 and verified against a SHA-256 embedded in the script. |
/etc/bilis-agent/config.yaml |
The generated Collector config. Mode 0644 — it holds no secrets; the key is read from the environment. |
/etc/bilis-agent/agent.env |
BILIS_API_KEY, BILIS_ENDPOINT, BILIS_HOST_NAME and BILIS_INTERVAL. Mode 0600, owned by root, loaded by systemd through EnvironmentFile=. |
/var/lib/bilis-agent/ |
The journald cursor and the persistent send queue, so nothing is lost across restarts or a Bilis outage. |
/etc/systemd/system/bilis-agent.service |
The hardened systemd unit. |
/usr/local/bin/bilis-agent |
A small CLI to check on and manage the agent — see below. |
It also creates a system user, bilis-agent, with no login shell. Nothing else
on the machine is touched.
What it collects
Host metrics
The host_metrics receiver scrapes every 60 seconds by default
(--interval changes it) with these scrapers: cpu, memory, load, disk,
filesystem, network, paging and processes (counts only — no
per-process series).
Noise that would multiply series without telling you anything is excluded:
- Filesystems of pseudo types:
tmpfs,overlay,squashfs,proc,sysfs,cgroup,devtmpfs,nsfsand the like. - Disks named
loop*andram*. - Network interfaces
lo,veth*,docker*andbr-*.
If the server is itself a container — an LXC or OpenVZ VPS plan, or an OrbStack
machine; the installer asks systemd-detect-virt --container — the root
filesystem sits on a block device the container cannot see, and the collector
would silently skip it. There the agent reports the root mount (/) only, which
is the disk the plan actually gives you.
A typical server produces around 100–200 series. At the default 60-second
interval that is roughly 150,000–300,000 data points a day — well under
the hosted Free plan's 1,000,000 metric data points a day (see
Limits and behavior). Halving the
interval to 30s doubles the count; every extra disk, filesystem or interface
adds a handful of series.
journald logs
The journald receiver reads the system journal from the moment of install
(start_at: end — it does not replay history) at priority info and above:
- The message becomes the log body.
- The severity comes from the journal's
PRIORITYfield —0(emerg) isFATAL,3isERROR,4isWARN,6isINFO,7isDEBUG— using the same mapping as Severity. service.nameis the systemd unit with.servicestripped (nginx,sshd,cron), or the syslog identifier when a line has no unit._PID,_COMMand_HOSTNAMEare kept as attributes.
The journal cursor is stored under /var/lib/bilis-agent, so a restart picks
up where it stopped instead of skipping or duplicating lines. --no-logs
turns this pipeline off and keeps metrics only.
Docker container stats
Only when /var/run/docker.sock exists at install time, the
docker_stats receiver reports per-container CPU, memory, network and block
I/O on the same interval as host metrics.
Warning: reading the Docker socket is root-equivalent — anything that can talk to it can start a privileged container. To collect container stats, the agent's user joins the
dockergroup, and the installer says so when it does. If you do not want that, install with--no-docker.
HTTP checks of local services
With one or more --check <url>, the http_check receiver requests each URL
on the same interval as host metrics, from the machine itself — so it can reach
services that are not public (http://localhost:8080/health, an internal
admin port). Each check reports:
httpcheck.duration— how long the request took, in ms.httpcheck.status—1, with the answer'shttp.status_codeandhttp.status_class(2xx,5xx, …) as attributes.httpcheck.error—1when there was no HTTP answer at all (DNS failure, refused connection, timeout), with the reason inerror.message.httpcheck.tls.cert_remaining— forhttps://URLs, seconds until the certificate expires.
A check made from the machine it watches cannot tell you that the machine is down, or unreachable from outside — it answers "is the app up on this box?".
The URLs are kept in /etc/bilis-agent/checks, so a re-run keeps them. A run
with --check replaces the whole list; --no-checks removes it. Because they
end up in the world-readable collector config, a check URL may not carry
credentials (user:pass@), quotes, spaces, $ or braces — use a
health endpoint that needs no credentials.
Where it shows up in Bilis
| Source | service.name |
Where to look |
|---|---|---|
| Host metrics | host |
Metrics → Hosts |
| Docker stats | docker |
Metrics → Hosts, under the host's Containers |
| HTTP checks | uptime |
Metrics explorer, by http.url |
| journald lines | the unit, e.g. nginx, sshd, docker, systemd |
Logs, filtered by service or host |
Metrics → Hosts lists every machine running the agent, one row each, with CPU, memory, the fullest filesystem and load over the window's last fifteen minutes, how many containers it runs, and when it was last heard from. Pick a row for its charts: CPU and memory by state, disk space by mount, disk and network throughput, and per-container CPU and memory. Each chart has an Open in explorer link with the same query, for filters and grouping of your own.
Every signal carries a host.name resource attribute — the machine's hostname,
or --host-name when you set one — and Docker stats carry container.name.
The explorer filters and groups on resource attributes as well as a data
point's own, so grouping any metric by host.name puts several machines on one
chart.
The agent turns on the host scraper's system.cpu.utilization,
system.memory.utilization and system.filesystem.utilization gauges, which
is what lets those charts read in percent. An agent installed before they were
added still fills every column of the Hosts table; re-run the installer to get
the percentage charts too.
Flags
Pass flags after sh -s --:
curl -fsSL https://your-bilis-host/install.sh \
| sudo BILIS_API_KEY=bilis_YOUR_API_KEY sh -s -- --interval 30s --no-docker
| Flag | What it does |
|---|---|
--key <key> |
The API key, instead of BILIS_API_KEY. Prefer the environment variable — it stays out of ps. |
--host-name <name> |
Overrides the host.name attribute. Defaults to the machine's hostname. |
--interval <dur> |
The scrape interval for host metrics and Docker stats, e.g. 30s, 2m. Default 60s. |
--no-docker |
Never collect container stats, even when Docker is installed. The agent stays out of the docker group. |
--no-logs |
Metrics only; no journald pipeline. |
--check <url> |
Request this http(s) URL every interval (repeatable). Replaces the URLs of a previous install. |
--no-checks |
Drop the check URLs a previous install was given. |
--dry-run |
Print the config and every action it would take, and change nothing. |
--uninstall |
Remove the agent — the same as bilis-agent uninstall. |
--purge |
With --uninstall, also delete /var/lib/bilis-agent. |
The bilis-agent command
| Command | What it does |
|---|---|
bilis-agent status |
Whether the service is running, where it sends, and how many exports failed in the last 15 minutes. |
bilis-agent logs [-f] |
The agent's own journal; -f follows it. |
bilis-agent test |
Sends one test data point (bilis.agent.test) with the configured key and reports what came back. |
bilis-agent restart |
Restarts the service, after validating the config. |
bilis-agent config |
Prints the generated Collector config. |
bilis-agent update |
Re-runs the installer from your Bilis host, keeping the key — see Updating. |
bilis-agent uninstall [--purge] |
Removes the agent — see Uninstalling. |
Security model
- A pinned, verified binary. The script downloads
otelcol-contribv0.159.0 from the official OpenTelemetry GitHub release and checks it against a SHA-256 for your architecture that is embedded in the script itself. A tampered download or a hostile mirror fails the install; nothing is fetched from Bilis except the script. - A dedicated user. The Collector runs as
bilis-agent, a system user without a login shell, in thesystemd-journalandadmgroups so it can read the journal — plusdockeronly when container stats are on. - systemd hardening.
NoNewPrivileges,ProtectSystem=strict,ProtectHome=read-only,PrivateTmp,ProtectKernelTunables,ProtectControlGroups,RestrictSUIDSGID,LockPersonality, an empty capability bounding set and a 256 MB memory ceiling. The only writable path is/var/lib/bilis-agent./procand/sysstay readable, because that is where host metrics come from. - The key lives in one file. It is written only to
/etc/bilis-agent/agent.env, mode0600, owned by root, and handed to the service through systemd'sEnvironmentFile=. The config refers to it as${env:BILIS_API_KEY}, so it never appears inconfig.yaml, in the process arguments or inps. - No listening ports. The Collector's own metrics endpoint is disabled and its logs go to the journal. The agent only makes outbound HTTPS requests to your Bilis host.
- A broken config fails loudly. The unit validates the config before every start, so a bad edit stops the service with a clear error rather than flapping.
The key is an ingest key: it can write to one project and read nothing. If a machine is compromised, revoke the key on the project page and nothing else is exposed.
Updating
Re-run the one-liner, or run bilis-agent update. The script upgrades in
place: without BILIS_API_KEY it keeps the key already in agent.env, stops
the service, swaps the files atomically and starts it again. The journal cursor
and the send queue survive, so no lines are lost or repeated.
Pass a new BILIS_API_KEY to rotate the key the same way.
Uninstalling
sudo bilis-agent uninstall
This stops and disables the service and removes the unit, the binary, the CLI
and /etc/bilis-agent. It keeps /var/lib/bilis-agent (the cursor and any
unsent queue) so a reinstall picks up where it left off; add --purge to delete
that too. Data already in Bilis stays until its retention expires.
Troubleshooting
Start with the agent's own two commands:
sudo bilis-agent test # does Bilis accept the key?
sudo bilis-agent logs -f # what is the Collector saying?
401frombilis-agent test— the key is wrong or has been revoked. Create a new one and re-run the one-liner with it.415— the Bilis instance has OTLP protobuf turned off (BILIS_OTLP_PROTOBUF=false). The installer detects this and switches the exporter to JSON, so you should not see it after a fresh install.503— Bilis is temporarily unable to store data. Nothing to do: the agent keeps the batch in its persistent queue and retries, honouringRetry-After.- No metrics after a few minutes — look for
Exporting failedinbilis-agent logs. The exporter uses the full…/api/v1/metricsand…/api/v1/logspaths explicitly; if you edited the config, see the note on endpoints in Metrics. - No container stats — Docker was not installed when the agent was, or you
passed
--no-docker. Re-run the one-liner once Docker is present. - The service will not start —
systemctl status bilis-agentshows the validation error from the pre-start config check.
Relation to the Linux host recipe
This agent is the quick path: metrics plus the journal, in one line, managed by
systemd. The Linux host page is the advanced,
hands-on recipe — a Collector in Docker that tails files in /var/log and
parses fail2ban, UFW and auth lines into filterable attributes. Use that one
for file-based logs the journal does not see; use this one for everything else.