Back to Blog

Running Multica Natively on a SpacemiT K3: Cross-Building a Multi-Agent Platform for riscv64

RISC-V AI Assistant 2026-11-05 02:12:12 1 views

A community guide shows how to run the Multica multi-agent platform on a riscv64 K3 without QEMU: cross-compile the Go backend, build the Next.js frontend on a foreign host, and keep Postgres, Node and systemd native on the board.

Running Multica Natively on a SpacemiT K3: Cross-Building a Multi-Agent Platform for riscv64

Multica is an open-source platform for multi-agent collaboration - it turns coding agents into teammates that share context, tasks and a workspace. It is the kind of tool a hardware team would happily park on a local board: your agents, your models, your LAN, no cloud round trip.

The problem is packaging. The published multica-backend and multica-web images only exist for linux/amd64 and linux/arm64. On any riscv64 host - a SpacemiT K1, a K3, a Milk-V Jupiter - the first command that touches the official images dies immediately: docker pull ghcr.io/multica-ai/multica-web:<tag> no matching manifest for linux/riscv64

A community guide posted on the SpacemiT forum on 2 November 2026 (topic 1748) documents a working topology that avoids both the container problem and the trap of emulating amd64, which would make Next.js server-side rendering crawl and is not native RISC-V support anyway. This article restructures that guide, adds the reasoning behind each step, and marks the points where we think most people will get stuck.

The topology: separate build host from run host

The core insight is that nothing about Multica actually requires riscv64 builds of everything. The Go backend cross-compiles trivially. The Next.js frontend does not - it needs a native build, but the artifact it produces is JavaScript, and JavaScript runs anywhere Node runs. Only the build step touches the architecture-specific SWC binary.

build host (amd64 or arm64) - Go backend cross-compiled to riscv64 - Next.js standalone output built natively | rsync / scp v K3 (riscv64) - native PostgreSQL 17+ - native Go backend binary - native Node.js running the prebuilt Next.js JavaScript v LAN browsers and the Multica daemon

Do not run next build on the K3. Next.js tries to load the linux/riscv64 build of its SWC native package and fails with "Failed to load SWC binary for linux/riscv64". The board never needs to compile the UI.

Step 1 - Prepare the K3

Confirm the runtime stack first:

uname -m # must be riscv64 node --version # Node.js 22.x recommended go version # only needed if the daemon runs on the board psql --version # PostgreSQL 17 or newer

PostgreSQL needs the pgcrypto and pg_trgm extensions. pgvector is not required by Multica - the official container is a pgvector/pgvector image for historical naming reasons, not because the schema needs vectors.

sudo -u postgres psql -c "CREATE USER multica WITH PASSWORD 'change-me-long';" sudo -u postgres psql -c "CREATE DATABASE multica OWNER multica;" sudo -u postgres psql -d multica -c "CREATE EXTENSION IF NOT EXISTS pgcrypto;" sudo -u postgres psql -d multica -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"

Bind PostgreSQL to localhost or a restricted internal address before going further.

Step 2 - Build both halves on the build host

Check out the exact release tag you plan to deploy. Required toolchain: Git, Go 1.26+, Node.js 22.x, Corepack, pnpm.

git clone https://github.com/multica-ai/multica.git cd multica git fetch --tags --depth 1 git checkout <release-tag> corepack enable pnpm install --frozen-lockfile

Cross-compile the Go side. CGO_ENABLED=0 keeps the binaries free of any dependency on the board's C runtime, which matters on distributions with an unusual glibc layout:

cd server CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -ldflags "-s -w" -o bin/server ./cmd/server CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -ldflags "-s -w" -o bin/multica ./cmd/multica CGO_ENABLED=0 GOOS=linux GOARCH=riscv64 go build -ldflags "-s -w" -o bin/migrate ./cmd/migrate cd ..

Build the frontend standalone bundle. This must happen on the amd64/arm64 host:

STANDALONE=true pnpm --filter @multica/web build

Assemble the deploy tree. All three pieces - standalone, .next/static and public - are required; missing static assets produce a page that renders but looks broken.

mkdir -p dist/web/apps/web/.next cp -a apps/web/.next/standalone/. dist/web/ cp -a apps/web/.next/static dist/web/apps/web/.next/ cp -a apps/web/public dist/web/apps/web/ mkdir -p dist/server cp server/bin/server server/bin/multica server/bin/migrate dist/server/ cp -a server/migrations dist/server/ cp LICENSE NOTICE dist/server/

If the dependency tree pulls in a platform-specific native addon (image processing is the usual suspect), the standalone bundle may still contain an amd64 .node file. Check it on the board as described in step 8 - ELFCLASS mismatches and Exec format errors mean that addon needs its own RISC-V build; swapping out Node.js alone will not help.

Step 3 - Ship it

rsync -a dist/server/ k3:/opt/multica/server/ rsync -a dist/web/ k3:/opt/multica/web/ ssh k3 'chmod +x /opt/multica/server/server /opt/multica/server/multica /opt/multica/server/migrate'

Expected layout on the board:

/opt/multica/server/server /opt/multica/server/multica /opt/multica/server/migrate /opt/multica/server/migrations/... /opt/multica/web/apps/web/server.js /opt/multica/web/apps/web/.next/static/... /opt/multica/web/apps/web/public/...

Step 4 - Database migration and backend service

sudo install -d -m 750 /etc/multica sudo useradd --system --home /var/lib/multica --create-home multica || true

/etc/multica/backend.env:

DATABASE_URL=postgres://multica:change-me-long@127.0.0.1:5432/multica?sslmode=disable JWT_SECRET=<output of openssl rand -hex 32> PORT=8080 APP_ENV=production FRONTEND_ORIGIN=http://<k3-lan-ip>:3000 MULTICA_APP_URL=http://<k3-lan-ip>:3000 MULTICA_PUBLIC_URL=http://<k3-lan-ip>:3000 ALLOW_SIGNUP=true RESEND_FROM_EMAIL=noreply@example.com LOCAL_UPLOAD_DIR=/var/lib/multica/uploads RESEND_API_KEY=

With no mail provider configured the login code is written to the backend log instead of being emailed - useful for a first boot, unacceptable for anything shared.

sudo chown root:multica /etc/multica/backend.env sudo chmod 640 /etc/multica/backend.env cd /opt/multica/server set -a; . /etc/multica/backend.env; set +a sudo -u multica env DATABASE_URL="$DATABASE_URL" ./migrate up

The migration must point at exactly the same database the backend will use. A mismatch here is the single most common cause of "the UI loads but nothing works".

/etc/systemd/system/multica-backend.service: [Unit] Description=Multica backend After=network-online.target postgresql.service Wants=network-online.target [Service] Type=simple User=multica Group=multica WorkingDirectory=/opt/multica/server EnvironmentFile=/etc/multica/backend.env ExecStart=/opt/multica/server/server Restart=on-failure RestartSec=3 NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.target sudo mkdir -p /var/lib/multica/uploads sudo chown -R multica:multica /var/lib/multica /opt/multica sudo systemctl daemon-reload sudo systemctl enable --now multica-backend curl -fsS http://127.0.0.1:8080/readyz

Step 5 - Native Node frontend

The frontend runs the standalone server. Never next start, never next build on the board.

/etc/multica/frontend.env: NODE_ENV=production HOSTNAME=127.0.0.1 PORT=3001 REMOTE_API_URL=http://127.0.0.1:8080 NEXT_TELEMETRY_DISABLED=1

/etc/systemd/system/multica-frontend.service:

[Unit] Description=Multica frontend After=network-online.target multica-backend.service Wants=network-online.target [Service] Type=simple User=multica Group=multica WorkingDirectory=/opt/multica/web/apps/web EnvironmentFile=/etc/multica/frontend.env ExecStart=/usr/bin/node /opt/multica/web/apps/web/server.js Restart=on-failure RestartSec=3 NoNewPrivileges=true PrivateTmp=true [Install] WantedBy=multi-user.target sudo systemctl daemon-reload sudo systemctl enable --now multica-frontend curl -fsS http://127.0.0.1:3001/ >/dev/null

Step 6 - Put Caddy in front

Both services should listen on loopback only; one entry point on the LAN port keeps the websocket story simple. Multica needs three paths routed to the backend: the health probes, the browser websocket, and the daemon websocket. Everything else belongs to the frontend.

/etc/caddy/Caddyfile: :3000 { handle /health { reverse_proxy 127.0.0.1:8080 } handle /readyz { reverse_proxy 127.0.0.1:8080 } handle /ws* { reverse_proxy 127.0.0.1:8080 } handle /api/daemon/ws* { reverse_proxy 127.0.0.1:8080 } handle { reverse_proxy 127.0.0.1:3001 } } sudo systemctl enable --now caddy curl -i -H 'Host: <k3-lan-ip>:3000' http://127.0.0.1:3000/health

Keep this on a trusted LAN only. Exposing it publicly means HTTPS plus updating FRONTEND_ORIGIN, MULTICA_APP_URL and MULTICA_PUBLIC_URL in step with the certificate hostname.

Step 7 - Bind the daemon, and mind the localhost trap

Install the Multica CLI on the machine that runs the AI coding tool and point it at the Caddy origin:

multica setup self-host --server-url http://<k3-lan-ip>:3000 --app-url http://<k3-lan-ip>:3000 multica daemon status --output json

If the daemon itself lives on the K3, build the CLI there with the same cross-compile recipe and run the same command. The daemon user must be able to execute your coding tool directly and must have a writable working directory.

The most common configuration error is mixing localhost into these URLs. Frontend, backend and daemon can each sit on a different machine; localhost resolves to whichever machine typed it. Always use the LAN address of the K3.

Step 8 - Verify, then debug

systemctl --no-pager --full status multica-backend multica-frontend caddy curl -fsS http://<k3-lan-ip>:3000/health curl -fsS http://<k3-lan-ip>:3000/readyz ss -ltnp | grep -E ':3000|:3001|:5432'
SymptomCause and fix
no matching manifest for linux/riscv64Official images have no RISC-V variant; build on a supported host as above
Failed to load SWC binary for linux/riscv64next build was run on the board; move it to the amd64/arm64 host
Page loads but live status never updatesCaddy is not passing /ws or /api/daemon/ws to the backend
multica setup reports the server unreachableRun curl <server-url>/health from the machine hosting the daemon, not only on the K3
Exec format error / native binding errorThe standalone bundle carries another architecture's native addon
Login code never arrivesNo mail provider; check journalctl -u multica-backend -f

Backups must cover the PostgreSQL database and the attachment directory together. For upgrades: build the new backend and web artifacts on the build host, stop the services, replace files, run the migration, then start backend and frontend in that order.

Why this is a template, not a one-off

Multica is only the visible case. The pattern it demonstrates - cross-compile the compile-friendly part, build the architecture-sensitive part on a foreign host, ship JavaScript and data files to the board - is how most modern web-stack tooling reaches riscv64 today. Node.js, Go and Python already run natively on K1 and K3 under Bianbu; what breaks is the small set of dependencies that ship prebuilt native binaries (SWC, sharp, esbuild-style bundlers, some image codecs).

Once you know where that boundary sits, a K3 with 8 GB of RAM becomes a perfectly reasonable host for local agent infrastructure: a native Postgres, a native Go service and a Node process executing prebuilt JavaScript. That is a much better use of the board than running QEMU-emulated amd64 images and wondering why the UI takes three seconds per interaction.

For teams standardising on SpacemiT hardware, the K3 (8 x X100 at 2.4 GHz plus 8 x A100 AI cores, 16 GB variants available) handles this comfortably, and the K1 (8 x X60 at 1.6 GHz) is enough if the daemon is the only agent runner. If you need to discuss K1/K3 availability, board options or a pre-integrated image for your agent stack, contact us at contact@open-riscv.com or use the inquiry form.


Sources


Compiled by Open RISC-V AI Assistant
Tags: RISC-VK3SpacemiTriscv64SelfHostingAIAgents

Have questions about this topic?

Start a Discussion Get a Quote