Deploy a module
There are several ways to deploy a module you wrote onto a machine. Which one to use depends on where you are in your software development cycle.
Pick a path
| You want to… | Path | When to use it |
|---|---|---|
| Test on one machine right now | Hot-reload | You wrote a module on your laptop and want to deploy it for testing on the compute machine for your robot. This is the fastest develop-deploy loop. The CLI builds the module, gets it onto the target machine, and adds it to the machine’s config in one command. |
| Release a versioned module for others (or for a fleet) | Versioned release | You want a stable version that any machine in your org (or any Viam user, if public) can install. This is what you do once your module is ready to share. |
Hot-reload gets your module onto a target machine and adds it to the machine’s config in one command. A versioned release is a tagged upload to the Viam registry, numbered with a semantic version like 0.1.0. Any machine in your org (or any Viam user’s machine, if public) can install it.
Hot-reload onto one machine
Hot-reload deploys your in-progress module to a single machine in one command: build, copy, add to the machine’s config. It’s faster than a versioned release because it builds only for the target’s platform, skips GitHub Actions runner setup, and tells the machine to restart the module right away with the new code instead of waiting for the next cloud sync.
The CLI provides two commands: viam module reload (build in the cloud) and viam module reload-local (build on your laptop). The procedure below uses viam module reload, which works regardless of your laptop’s architecture. To skip the cloud round-trip when your laptop matches the target, see Build on your laptop with reload-local at the end of this section.
Run it:
Find your machine’s part ID first. At the top of the machine’s page, click the Live / Offline status dropdown, then click Part ID to copy it.
In the command below:
--model-nameis the full model identifier from yourmeta.json, in the formnamespace:module-name:model-name. Copy it from themodelfield of the entry inmeta.json’smodelsarray. If your module declares more than one model, pick the one you want to add to the machine.--resource-namenames the component or service that the model adds to the machine — a component (sensor, motor, camera, and so on) or a service (ML model, vision, motion, and so on), depending on which API the model implements. Pick any unique string. It appears on the CONFIGURE tab and is how you reference the component or service from client code.
From your module’s root directory (where meta.json lives), run:
viam module reload --part-id <machine-part-id> --model-name <namespace:module-name:model> --resource-name <resource-name>
What to expect:
- The CLI prints build progress, then upload progress, then a success line.
- The machine’s CONFIGURE tab shows the new component or service with the name you passed to
--resource-name. Open it and set the attributes your module expects. - The module starts within a few seconds. The LOGS tab shows a
Module successfully addedentry with your module name.
On each code change: rerun the same command to deploy the new code.
Build on your laptop with reload-local
If your laptop and the target have the same architecture (for example, both linux/arm64), reload-local builds on your laptop and copies the archive directly to the target over the network. No cloud round-trip.
For cross-architecture deploys, use cloud reload rather than reload-local. Python in particular has no alternative because PyInstaller can’t cross-compile.
The flags are the same as reload. From your module’s root directory:
viam module reload-local --part-id <machine-part-id> --model-name <namespace:module-name:model> --resource-name <resource-name>
The target machine must be online (visible in the Viam app). The CLI connects over WebRTC, authenticates with your viam login session, and uses the machine’s shell service to copy the archive. You don’t need LAN access, a VPN, SSH keys, or port forwarding.
Useful flags:
--no-buildskips the build step if you already built the archive manually withbash build.sh.--localruns the module from source files on your laptop instead of shipping a tarball. Use this only when the target machine is your laptop. In--localmode,viam module restartpicks up Python source edits without a rebuild.
For the full hot-reload walkthrough including how it fits into the development loop, see Test locally on the driver-module page.
When you’re ready to share the module beyond the one machine you tested on, do a versioned release.
Release a versioned module
If you’ve been testing with viam module reload, your code is already running on a single target machine. A versioned release publishes the same code as a numbered package in the Viam registry. Any machine in your org can install it (or any Viam user’s machine, if the module is public). Machines that aren’t pinned to a specific version pick up new releases automatically, within a minute or two.
Two ways to build and upload:
- Cloud build (recommended). Viam compiles your module for every target platform from your GitHub repo. Trigger it manually with one CLI command, or set up GitHub Actions to auto-build on every release tag. No local cross-compilation.
- Manual upload. You build locally and run
viam module upload. You’re responsible for cross-compiling for each target platform yourself.
Both paths share the same prep work first.
Before you start
For cloud build (recommended): create a Viam organization API key in the Viam app at your org’s Settings page. The key value is shown once and can’t be retrieved later, so save it before navigating away. You’ll also need your module’s code in a GitHub repo with the url set in meta.json; if you don’t have one yet, you’ll set it up in Publish with cloud build below.
For manual upload: you need a way to build a binary for each platform you want to support. Cross-compiling from linux/amd64 to linux/arm64 requires GOOS/GOARCH for Go or building on the target architecture for Python (PyInstaller does not cross-compile).
Update meta.json for publishing
The Viam module generator created a working meta.json in your module directory. Before publishing, update these fields if needed:
visibility: defaults toprivate(only your organization can install). Change topublicif you want any Viam user to install your module, orpublic_unlistedto make it reachable by ID but not listed in registry search results. Public visibility requires your organization to have a public namespace, set up at your org’s Settings page in the Viam app.url: link to the source repository. Required for cloud build — set this to your GitHub repo’s URL.description: shown in the registry UI and search results. Replace the generator default with what your module actually does.markdown_link(optional): path to a README. If your module is going public, a README is essential. For starter templates, see README templates at the bottom of this page.
For the full meta.json field reference, see Module reference.
Review the build scripts
The generator creates build and setup scripts. The defaults work for typical Python and Go modules. Review and customize them if you need additional build steps (for example, compiling native extensions or installing system packages).
| File | Purpose |
|---|---|
setup.sh | Installs Python dependencies from requirements.txt |
build.sh | Packages the module into a .tar.gz archive |
run.sh | Entrypoint script that starts the module |
| File | Purpose |
|---|---|
setup.sh | Installs build dependencies (Go modules are typically self-contained) |
build.sh | Cross-compiles the binary and packages it into a .tar.gz archive |
Makefile | Local build targets |
The generated build.sh uses GOOS and GOARCH environment variables to cross-compile for the target platform. Cloud build sets these automatically.
With prep complete, follow one of the two sections below to publish your module. Cloud build is recommended for most cases; use a manual upload only when cloud build isn’t an option.
Publish with cloud build (recommended)
Cloud build is a Viam-side build service that compiles your module from your GitHub repo for every target platform listed in meta.json’s build.arch. Both paths below require your module to be in a GitHub repo with the URL set in meta.json.
If you don’t have a GitHub repo yet, push your module’s code to one. From your module’s root directory:
git init
git add .
git commit -m "Initial module code"
git remote add origin <your-repo-url>
git push -u origin main
There are two ways to start a build: a one-shot build from the CLI for a single release, or auto-build on every GitHub release for ongoing releases.
One-shot build from the CLI
Use this for a single release without setting up GitHub Actions. From your module’s root directory:
viam module build start --version 0.1.0
The --version value is what the package will be tagged as in the registry. Increment it for each release.
Non-main default branches
Cloud build expects your repository’s default branch to be main. If your repository uses master (or another branch), pass the --ref flag:
viam module build start --ref master --version 0.1.0
What to expect:
- The CLI prints a build ID and exits. The build runs in Viam’s cloud.
- Follow the logs with
viam module build logs --id <build-id>. - On success, your module appears at
https://app.viam.com/module/<namespace>/<module-name>with the version you passed to--version.
Auto-build on every GitHub release
Use this for ongoing releases. Set up once, then every new release tag triggers a build automatically. The generator’s .github/workflows/deploy.yml workflow uses the viamrobotics/build-action GitHub action to start the build.
1. Add Viam credentials as GitHub secrets.
- In your GitHub repository: Settings → Secrets and variables → Actions.
- Add two repository secrets:
VIAM_KEY_ID: your API key IDVIAM_KEY_VALUE: your API key value
2. Tag a release.
git tag v0.1.0
git push origin v0.1.0
What to expect:
- The GitHub Action starts immediately. Watch progress in the Actions tab of your GitHub repo. A typical first build takes 5-15 minutes (longer for the first run because dependencies aren’t cached).
- When the workflow turns green, your module appears at the registry with the tagged version.
- If the workflow fails, click into the run for the build log. See the Cloud build fails in GitHub Actions entry under Troubleshooting for the common causes.
Publish with a manual upload
Use this when you can’t use cloud build (no GitHub repo, restricted CI, etc.). You build locally and upload one archive per target platform.
You must build for the target platform
The binary in your archive must already be compiled for the target machine’s OS and architecture. If you build on an x86 laptop and upload as linux/arm64 without cross-compiling, the module will fail with exec format error on ARM machines.
1. Build for each target platform:
PyInstaller doesn’t cross-compile, so each platform you want to support needs its own build run on a machine of that architecture. (Or use cloud build, which handles this for you.)
cd my-sensor-module
bash build.sh
The generated build.sh uses PyInstaller to compile your module into a standalone executable containing the Python interpreter and all dependencies.
PyInstaller limitation: relative imports
PyInstaller does not support relative imports in entrypoints (imports starting with .). If you get ImportError: attempted relative import with no known parent package, see the PyInstaller workaround.
cd my-sensor-module
GOOS=linux GOARCH=arm64 go build -o dist/module cmd/module/main.go
tar -czf dist/archive.tar.gz -C dist module
Set GOARCH to match your target machine: amd64 for x86_64, arm64 for ARM.
2. Upload the archive:
viam module upload --version=0.1.0 --platform=linux/arm64 dist/archive.tar.gz
3. Repeat for each platform you want to support:
On each target architecture, build and upload separately:
bash build.sh
viam module upload --version=0.1.0 --platform=<linux/amd64-or-linux/arm64> dist/archive.tar.gz
GOOS=linux GOARCH=amd64 go build -o dist/module cmd/module/main.go
tar -czf dist/archive.tar.gz -C dist module
viam module upload --version=0.1.0 --platform=linux/amd64 dist/archive.tar.gz
What to expect:
- Each
viam module uploadprintsVersion successfully uploaded! you can view your changes online here: <url>on success. - Your module appears at the registry with the uploaded version. Each platform you uploaded is listed under that version.
- Before uploading, the CLI checks that the entrypoint exists in the archive and has execute permissions. If either check fails, the CLI stops the upload. The CLI also warns (but does not block) if the archive contains symlinks that point outside the archive. To skip the entrypoint checks (not recommended for production), pass
--force.
Configure on a machine
With your module in the registry, any machine in your org can use it (and any Viam user’s machine, if the module is public).
In the Viam app, open your machine’s CONFIGURE tab.
Click + and select Configuration block.
Search for your module by name or browse the registry, then add it.
Pick a model from your module and create an instance. Name the instance and configure its attributes:
{ "source_url": "https://api.example.com/sensor/data" }Click Save.
What to expect:
viam-serverdownloads the module from the registry within a few seconds.- The LOGS tab shows a
Module successfully addedentry with your module name. - Open the CONTROL tab and verify the component responds.
- If the module fails to start, the LOGS tab shows the error. See the Module works locally but fails after deployment entry under Troubleshooting below.
That’s the first deploy. To release new versions, pin a machine to a specific version, change visibility, restrict uploads to specific platforms, or perform other lifecycle operations, see Update and manage modules.
Troubleshooting
Reference
README templates
You can point your module’s registry page to a README by setting the markdown_link field in meta.json to a file path (for example, README.md) or a section anchor (for example, README.md#my-sensor).
Was this page helpful?
Glad to hear it! If you have any other feedback please let us know:
We're sorry about that. To help us improve, please tell us what we can do better:
Thank you!