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.
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/riscv64A 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 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 daemonDo 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.
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 newerPostgreSQL 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.
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-lockfileCross-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 buildAssemble 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.
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/.../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 upThe 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/readyzThe 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/nullBoth 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/healthKeep 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.
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 jsonIf 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.| Symptom | Cause and fix |
|---|---|
| no matching manifest for linux/riscv64 | Official images have no RISC-V variant; build on a supported host as above |
| Failed to load SWC binary for linux/riscv64 | next build was run on the board; move it to the amd64/arm64 host |
| Page loads but live status never updates | Caddy is not passing /ws or /api/daemon/ws to the backend |
| multica setup reports the server unreachable | Run curl <server-url>/health from the machine hosting the daemon, not only on the K3 |
| Exec format error / native binding error | The standalone bundle carries another architecture's native addon |
| Login code never arrives | No 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.
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.