If you ship software to Rockchip boards — RK3588, RK3576, RK356x — you
know the ritual. Build a tarball. scp it to the board. SSH in, untar
it somewhere, write a systemd unit by hand, daemon-reload, restart,
tail the journal, and pray. Now do it nineteen more times. Now do it
again for the hotfix. Now one board is still running the old version
and nobody knows which.
I kept living that ritual, so I turned it into a tool:
rk-deploy — manifest in,
systemd services out, with fleet push, rollback and status reports.
v0.1.0 is out today, Apache-2.0.
The shape of it
You describe the app once:
name: demo version: 1.1.0 runtime: type: python entry: app.py models: - path: models/weights.rknn healthcheck: type: file path: "@INSTALL_ROOT@/shared/heartbeat.json" timeout_sec: 15 Then:
rkdeploy pack # checksummed tarball rkdeploy deploy --fleet fleet.yaml demo-1.1.0.tar.gz # push to every board rkdeploy rollback --fleet fleet.yaml --app demo # back, fleet-wide rkdeploy status --fleet fleet.yaml --app demo # one probe per board Rules I was not willing to compromise on:
- Only what the manifest declares gets packed. Whatever else is lying in your directory — notes, stray secrets — does not travel.
- Versions are immutable, switching is atomic. Each release unpacks into its own directory; going live is one symlink swap (a fresh symlink +
mv -Tf—ln -sfnon a symlink-to-directory is a trap that follows the link and creates the new one inside the target). - Config and data live outside releases, in
shared/, so upgrades and rollbacks never touch them. - The board stays dumb. All parsing happens on your machine. The board runs one POSIX shell installer and stock systemd — because board Python stacks vary wildly between BSPs (the RK3588 I test on has Python 3.10 and a stray PyYAML 5.4.1; the next board may have neither), the installer reads a flat rendered
package.env, never YAML, never even JSON. - No agent, no new ports, no credential collection. Plain SSH with your keys, one upload + one execution per board per deploy.
- A failed health check rolls back automatically, and the exit code tells you exactly which world you're in: 0 ok, 4 the package failed verification, 5 part of the fleet failed, 6 the deploy failed but the board is back on the previous version, 7 it failed and rollback failed too.
The bug my own test suite caught before any board did
The demo app writes a heartbeat file every second — the perfect
health check. Except: during an upgrade, the old version's process
is still running until the restart, happily refreshing that same
heartbeat file. An existence check would pass the brand-new version
before its process wrote a single byte.
So for file health checks, rk-deploy also requires the file to
have been written at or after this deploy's restart moment. A
one-line rule, found by a lifecycle test that runs the real board
scripts against a fake systemctl which actually starts the app —
install, upgrade, duplicate-version refusal, rollback, broken
release, auto-rollback, all of it, no hardware needed.
Real board, real numbers
Validated on an RK3588 (Ubuntu 22.04, systemd 249), driving the
shipped CLI over a tunnel — where every SSH connection costs 5–7
seconds just to establish, which is itself the best argument for the
one-connection-per-operation design:
- deploy 1.0.0 → ok, 21 s (three connections: mkdir, scp, install)
- status → ATTENTION — only one release installed, i.e. no rollback candidate yet. The warning is the spec's own rule firing.
- deploy 1.1.0 → ok, 19 s; status → HEALTHY (SoC 49 °C)
- deploy a deliberately broken 1.2.0 → health check fails, auto-rollback to 1.1.0, exit 6, 26 s
- explicit rollback to 1.0.0 → ok, fleet HEALTHY
A genuine status report from that run ships in the repo under
examples/. And the board's systemctl --failed afterwards?
Identical to before the test — the tool cleans up after itself in
validation and stays out of everything it doesn't manage in
production. One related call: status judging uses only your app's
unit state, never the machine-wide is-system-running — a board can
sit at degraded for months because of some unrelated logrotate
unit while your app is perfectly healthy.
What it deliberately doesn't do
No resident agent, no central server, no web UI. No automatic
apt install — dependencies are a preflight checklist (binaries /
shared libs / python modules / packages / devices), reported
precisely, because customer boards are often offline and silently
mutating system packages on an unattended board is worse than a
clear report. No data migration on rollback — if your app changes
its on-disk format incompatibly, that's your app's problem, and the
docs say so in exactly those words, because it's precisely the thing
people assume a deploy tool does for them.
rk-deploy is the third tool in a small open family for Rockchip edge
work, next to
rk-edge-doctor
(read-only board / model / RTSP diagnostics) and
rk-bench (a measured RKNN
model leaderboard). Diagnosis, measurement — and now delivery.
If you deploy to RK boards for a living, I'd genuinely like to hear
what your ritual looks like and what would break first in your
environment: RK3576 and RK356x board reports are the most useful
thing you can file.
