
If you're collecting telemetry from field-deployed hardware (drones, robotics platforms, industrial controllers, edge compute modules), the database question eventually lands: what catches the data on the ground side?
Most answers involve InfluxDB. And most teams running InfluxDB at the edge eventually run into the same friction: resource overhead, cardinality limits at scale, and a migration story that changes with every major version.
This post walks through deploying Arc on ARM edge hardware. No JVM. No external dependencies. One binary. Every command below is against Arc v26.06.3, the current release.
What edge deployment actually means here
Edge in this context means a Linux host that is:
- ARM64 (Raspberry Pi 4/5, NVIDIA Jetson, Ampere-based gateways, ARM industrial gateways). Arc ships 64-bit ARM only. There is no 32-bit
armv7/armhfbuild, so a Pi Zero, a Pi 2, or a 32-bit-userland Zynq board is out of scope. On a Pi 4 or 5, make sure you're running a 64-bit OS:uname -mmust printaarch64, notarmv7l. - Resource-constrained relative to a cloud VM: typically 2–8 GB RAM, limited NVMe or flash storage
- Potentially air-gapped or operating in a degraded network environment (DIL: degraded, intermittent, limited connectivity)
- Receiving data from sensors, controllers, or other devices via MQTT, Telegraf, or direct HTTP
Arc runs in all of these environments. Here's exactly how.
What Arc ships as
Arc is a single Go binary. On Linux ARM64, the v26.06.3 release includes:
- A native
arc-linux-arm64binary arc_26.06.3_arm64.debfor Debian/Ubuntu-based ARM systemsarc-26.06.3-1.aarch64.rpmfor RHEL/Fedora/Rocky-based ARM systems- A multi-arch container image (
ghcr.io/basekick-labs/arc:26.06.3) built forlinux/amd64andlinux/arm64 - A FIPS variant of each of the above (
arc-fips-linux-arm64,arc-fips_26.06.3_arm64.deb,arc-fips-26.06.3-1.aarch64.rpm)
Every binary ships with a cosign .bundle for signature verification, and the release carries SBOMs and SLSA provenance.
There is no JVM. No Python runtime. No Postgres dependency. No Zookeeper. No Kafka. The binary bundles ingestion, storage, compaction, query, retention, and continuous query scheduling.
One caveat on "statically linked": Arc embeds a C++ query engine, so the binary is cgo-based rather than a pure static Go build. In practice this doesn't change the deployment story. It runs on a stock Debian/Ubuntu/RHEL ARM64 userland with no packages to install, but it isn't the kind of binary you drop into a FROM scratch container.
Installing Arc on an ARM64 Linux gateway
Option 1: Native binary
# Download the ARM64 binary
wget https://github.com/Basekick-Labs/arc/releases/download/v26.06.3/arc-linux-arm64
chmod +x arc-linux-arm64
# Configure via environment (see the config section below for arc.toml)
export ARC_STORAGE_BACKEND=local
export ARC_STORAGE_LOCAL_PATH=/mnt/data/arc
export ARC_SERVER_PORT=8000
./arc-linux-arm64Arc has no configuration command-line flags. This trips people up coming from other databases. The binary takes exactly one subcommand, arc compact, for manual compaction. Everything else is configured through arc.toml or environment variables. There is no --port, no --storage-path. Environment variables map onto the config keys by prefixing ARC_ and replacing dots with underscores, so storage.local_path becomes ARC_STORAGE_LOCAL_PATH.
Arc looks for arc.toml in the working directory, then /etc/arc/, then $HOME/.arc/.
Option 2: .deb package (Debian/Ubuntu ARM)
wget https://github.com/Basekick-Labs/arc/releases/download/v26.06.3/arc_26.06.3_arm64.deb
sudo dpkg -i arc_26.06.3_arm64.deb
sudo systemctl enable arc
sudo systemctl start arcThe package installs the binary at /usr/bin/arc, a default config at /etc/arc/arc.toml, a systemd unit at /lib/systemd/system/arc.service, and a data directory at /var/lib/arc. The service runs as the unprivileged arc user. Edit /etc/arc/arc.toml and systemctl restart arc to apply changes.
The RPM path is identical:
wget https://github.com/Basekick-Labs/arc/releases/download/v26.06.3/arc-26.06.3-1.aarch64.rpm
sudo rpm -i arc-26.06.3-1.aarch64.rpm
sudo systemctl enable --now arcNote that the arc and arc-fips packages both ship /usr/bin/arc and the same arc.service, and are declared to conflict. Install one or the other, never both.
Option 3: Docker (if the edge host runs a container runtime)
docker run -d \
--name arc \
--restart unless-stopped \
-p 8000:8000 \
-e ARC_STORAGE_BACKEND=local \
-v /mnt/data/arc:/app/data \
ghcr.io/basekick-labs/arc:26.06.3On resource-constrained hardware, native binary deployment avoids the container runtime's memory and startup overhead. Pin the version tag rather than :latest on an edge box. An unattended docker pull on a device you can't easily reach is not a good trade.
Tuning arc.toml for constrained hardware
Arc's defaults auto-size from the host: query engine memory defaults to roughly 50% of system RAM, thread count to the CPU core count, and max connections to 2× cores. On a 4 GB / 4-core gateway those defaults are aggressive, because they assume the box exists to run Arc and nothing else. On an edge node that's also running an inference workload or a control loop, pin them explicitly:
[server]
port = 8000
[log]
level = "info"
format = "json"
[storage]
backend = "local"
local_path = "/mnt/data/arc"
[database]
memory_limit = "1GB" # Hard ceiling to protect the OS from the query engine
thread_count = 2 # Leave cores for ingestion and the rest of the system
max_connections = 4
enable_wal = true
[ingest]
max_buffer_size = 50000 # Records buffered before flush
max_buffer_age_ms = 5000 # ...or flush after 5s, whichever comes first
[compaction]
enabled = true
hourly_enabled = true
hourly_schedule = "5 * * * *"
hourly_min_files = 10
daily_enabled = true
daily_schedule = "0 3 * * *"
[auth]
enabled = true
[wal]
enabled = falseA few notes on keys that are easy to get wrong:
- The ingestion section is
[ingest], not[ingestion], and the keys aremax_buffer_size(a record count, not bytes) andmax_buffer_age_ms(milliseconds, not seconds). The shipped sample sets a very large buffer tuned for server-class ingest benchmarks; on an edge box a smaller buffer with a several-second age bound is the better trade: it caps RAM and bounds how much unflushed data a power cut costs you. - Query engine tuning lives under
[database](memory_limit,thread_count,max_connections). There is no separate engine section. - Compaction has no single
schedulekey. It has independent hourly and daily jobs with their own cron expressions, both enabled by default. [wal]is disabled by default. Turning it on costs write throughput and flash writes but bounds data loss on an unclean shutdown. On a vehicle or a device with no UPS, that's often worth it. Note this is Arc's ingest WAL and is separate fromdatabase.enable_wal, which is the query engine's own WAL for its metadata database.
Getting MQTT data into Arc
The most common edge pattern: sensors publish to a local MQTT broker (Mosquitto), and data flows from there into Arc.
Path 1: Arc native MQTT subscriber
Arc has had a native MQTT subscriber since v26.02.1. It dials out to an existing broker and subscribes to topics directly, with no Telegraf in the middle, one less process on a box where process count matters.
Unlike most of Arc's configuration, subscriptions are not defined in arc.toml. The TOML section is a feature toggle only; subscriptions are created through the REST API and persisted in Arc's SQLite catalog, so they survive restarts and can be changed at runtime without bouncing the server.
Enable the subsystem:
[mqtt]
enabled = trueOr ARC_MQTT_ENABLED=true. Restart Arc, then confirm:
curl http://localhost:8000/api/v1/mqtt/healthA 200 with {"status":"healthy", ...} means it's up. A 200 with {"status":"disabled"} means the toggle didn't take.
Then create a subscription:
curl -X POST http://localhost:8000/api/v1/mqtt/subscriptions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $ARC_TOKEN" \
-d '{
"name": "field-sensors",
"broker": "tcp://localhost:1883",
"topics": ["sensors/#", "telemetry/#"],
"database": "iot",
"qos": 1,
"auto_start": true
}'Arc auto-detects JSON and MessagePack payloads. auto_start: true reconnects the subscription on restart, with exponential backoff on connection loss, the behavior you want on an intermittent link. Broker passwords are encrypted at rest with AES-256-GCM, TLS with client certificates is supported via tls_cert_path/tls_key_path/tls_ca_path, and topic_mapping extracts tags from topic path segments.
Lifecycle and stats are API-driven too:
curl http://localhost:8000/api/v1/mqtt/subscriptions \
-H "Authorization: Bearer $ARC_TOKEN"
curl -X POST http://localhost:8000/api/v1/mqtt/subscriptions/{id}/stop \
-H "Authorization: Bearer $ARC_TOKEN"Path 2: Telegraf mqtt_consumer to Arc
If you already run Telegraf, keep it. Arc has a native Telegraf output plugin that writes MessagePack columnar format, which is meaningfully faster than Line Protocol:
# telegraf.conf
[[inputs.mqtt_consumer]]
servers = ["tcp://localhost:1883"]
topics = [
"sensors/#",
"telemetry/#"
]
data_format = "influx"
[[outputs.arc]]
url = "http://localhost:8000/api/v1/write/msgpack"
api_key = "$ARC_TOKEN"
content_encoding = "gzip"
database = "sensors"The outputs.arc plugin requires a recent Telegraf. The integration docs cite 1.37+, so check telegraf --version before assuming it's present.
If you're on an older Telegraf, or mid-migration and dual-writing to InfluxDB, the InfluxDB v2 output works against Arc's compatibility endpoint. The bucket becomes the Arc database and organization is ignored:
[[outputs.influxdb_v2]]
urls = ["http://localhost:8000"]
token = "$ARC_TOKEN"
organization = ""
bucket = "sensors"That points at /api/v2/write, which Arc serves for InfluxDB compatibility. No changes to your MQTT broker, no changes to your inputs. The URL and output plugin are the only things that change.
Path 3: Direct HTTP ingestion
For devices that write HTTP directly:
# InfluxDB 1.x compatible endpoint: db is a query parameter
curl -X POST "http://localhost:8000/write?db=iot" \
-H "Authorization: Bearer $ARC_TOKEN" \
--data-binary "sensor_data,device_id=unit_042,location=bay_3 temperature=72.4,vibration=0.02 $(date +%s%N)"Arc exposes three Line Protocol write paths, and the difference matters because each takes the target database differently:
| Endpoint | Database comes from | Use for |
|---|---|---|
POST /write | ?db= query param, or x-arc-database header | InfluxDB 1.x clients, Telegraf influxdb output |
POST /api/v2/write | ?bucket= query param, or x-arc-database header | InfluxDB 2.x clients, influxdb_v2 output |
POST /api/v1/write/line-protocol | x-arc-database header only | Arc-native clients |
All three default to the default database if nothing is specified. There is no plain /api/v1/write endpoint. That path 404s.
Timestamp precision defaults to nanoseconds and is set with ?precision=, accepting ns, us, ms, or s. The $(date +%s%N) above is nanoseconds, matching the default. On BSD-derived or minimal userlands where date +%s%N isn't supported, send ?precision=s and $(date +%s) instead.
For the highest throughput, use the MessagePack columnar endpoint at /api/v1/write/msgpack with the x-arc-database header. That's the path the Telegraf plugin and the Python SDK use.
Handling disconnected operations (DIL environments)
Arc writes to local storage first. If the edge node loses connectivity to a central store, it keeps ingesting and querying against local Parquet. There is no quorum to lose and no cloud endpoint in the write path. That's the baseline behavior in v26.06.3, and for many deployments it's enough: configure local storage as the backend, and handle upstream sync at the infrastructure layer with rsync, rclone, or your own agent.
Arc 26.09.1 adds Edge Sync, a first-class spoke-to-hub transport built for exactly this problem. It ships on August 31, 2026, and is in final stabilization testing as this post goes up, so treat what follows as what's landing within days rather than what you can wget right now.
The design ships files rather than rows: Arc's Parquet files are immutable and content-addressed with a per-file SHA256, so the sync unit is the file, integrity is free, and re-delivering a file converges to the same state instead of double-counting rows. The spoke always dials out to the hub, which suits edges behind NAT or on links that can't accept inbound connections. Discovery is one batched reconcile round-trip; transfer is N thin per-file streams, each independently resumable across contact windows. The hub is just another Arc instance with a receive endpoint enabled.
There's also an air-gap path: bundle export writes a set of files to an allow-listed directory, typically a USB drive, for import on the hub, for sites where the link isn't thin but absent.
Two things to know before planning around it. Manual sync is open source; the connectivity-adaptive background loop is an Enterprise feature. And the spoke's credentials are environment-only by design: ARC_EDGE_SYNC_SPOKE_SECRET and ARC_EDGE_SYNC_HUB_TOKEN are deliberately not config keys, and Arc refuses to start if it finds a secret in arc.toml rather than quietly preferring the environment.
The full architecture is in Designing Arc Edge Sync.
Flash storage considerations
Edge gateways running on flash (eMMC, SD card) have write endurance limits. What helps:
- Raise
max_buffer_age_ms. Fewer, larger flushes mean fewer, larger Parquet files and less write amplification. The trade is data loss exposure on power cut: with the ingest WAL off, an unflushed buffer is gone. - Keep compaction on its scheduled cadence. Compaction reads and rewrites files, which is exactly the I/O flash hates, but the alternative, thousands of small files, makes every query slower and costs more total I/O over time. The hourly/daily defaults already batch it rather than running continuously; if the box is busy at the top of the hour, move
hourly_schedulerather than disabling it. - Mount data on NVMe or an external SSD when available, not the OS flash partition.
storage.local_pathis the only setting involved. - Set retention policies so old data is dropped rather than accumulating. Retention is enabled by default; policies are configured per database.
Checking what's running
# Verify Arc is up (public endpoint, no auth required)
curl http://localhost:8000/health
# Write a test point
curl -X POST "http://localhost:8000/write?db=default" \
-H "Authorization: Bearer $ARC_TOKEN" \
--data-binary "test,host=edge01 value=1"
# Query it back. Note the field is "sql", not "query"
curl -X POST http://localhost:8000/api/v1/query \
-H "Authorization: Bearer $ARC_TOKEN" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT * FROM test ORDER BY time DESC LIMIT 5", "format": "json"}'/health and /ready are public routes even with auth enabled, which makes them usable as systemd or container health checks without embedding a token.
FIPS deployments
If your edge hardware is part of a program that requires FIPS 140-3, Arc ships a separate arc-fips build as of v26.06.2, and the ARM64 variant is available: arc-fips-linux-arm64, plus matching .deb and .rpm packages.
It's the same source at the same version as the standard build, compiled with a fips build tag against the CMVP-certified Go Cryptographic Module v1.0.0, running in FIPS-only mode (GODEBUG=fips140=only is baked in). TLS for the API, cluster, and MQTT paths is restricted to FIPS-approved cipher suites and curves. API token hashing uses PBKDF2-HMAC-SHA256 instead of bcrypt, which is not FIPS-approved. The binary fails closed at startup if it isn't actually running in FIPS mode.
Two operational notes:
Rotate your tokens on cutover. Tokens created by a non-FIPS build are stored as bcrypt hashes, and the FIPS binary refuses to verify them. Recreate existing API tokens after switching so they're re-stored as PBKDF2. New tokens are PBKDF2 automatically.
Be precise about what's validated. The cryptographic module Arc compiles against is CMVP-certified. Arc itself is not a CMVP-listed module, and this is not a claim that "Arc is FIPS 140-3 validated." Confirm the live certificate number on the NIST CMVP list before relying on it in a compliance submission.
What you end up with
A single Go binary on your ARM64 gateway, ingesting MQTT natively or through Telegraf, writing compressed Parquet to local flash or NVMe, queryable in SQL over HTTP or Grafana, with a FIPS build available for regulated programs.
The same binary, same SQL, same Parquet format runs identically on a laptop, an edge gateway, and a cloud VM. When you're ready to move data to a central store, it's already in open Parquet. No export step, no format conversion.
Arc is open source under AGPL-3.0. Download the ARM64 binary from basekick.net/download or GitHub Releases.
Running InfluxDB at the edge and evaluating a move? Start with Arc vs InfluxDB.