# Building from Source
Source: https://docs.make87.com/advanced/building-from-source
Build m87 CLI and runtime from source code
Building m87 from source gives you the latest development features and allows you to customize the build for your specific needs.
## Prerequisites
Before building m87, ensure you have the required tools installed.
### Required Tools
m87 requires Rust 1.85 or later. Install or update Rust using rustup:
```bash theme={null}
# Install rustup (if not already installed)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# Update to latest stable
rustup update stable
# Verify version
rustc --version
```
Expected output: `rustc 1.85.0` or higher.
Git is required to clone the repository:
```bash theme={null}
# Verify git is installed
git --version
# Install if needed (Debian/Ubuntu)
sudo apt-get install git
# Install if needed (macOS)
brew install git
```
Install system dependencies required for compilation:
**Debian/Ubuntu:**
```bash theme={null}
sudo apt-get update
sudo apt-get install build-essential pkg-config libssl-dev
```
**Fedora/RHEL:**
```bash theme={null}
sudo dnf install gcc pkg-config openssl-devel
```
**macOS:**
```bash theme={null}
# Xcode Command Line Tools
xcode-select --install
```
## Quick Build
For most users, the standard build process is:
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
cargo build --release
```
The compiled binary will be at `target/release/m87`.
## Detailed Build Instructions
Clone the m87 repository from GitHub:
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
```
To build a specific version:
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
git checkout v0.x.x # Replace with desired version
```
Build the release version:
```bash theme={null}
cargo build --release
```
This compiles m87 with full optimizations. The build process may take several minutes.
The `--release` flag enables optimizations and produces a smaller, faster binary. Development builds (without `--release`) are faster to compile but slower to run.
After compilation, the binary is located at:
```bash theme={null}
ls -lh target/release/m87
```
Copy the binary to a directory in your PATH:
```bash theme={null}
# Copy to user bin directory
cp target/release/m87 ~/.local/bin/
# Or copy to system bin directory (requires sudo)
sudo cp target/release/m87 /usr/local/bin/
# Verify installation
m87 --version
```
Ensure `~/.local/bin` is in your PATH. Add to your shell profile if needed:
```bash theme={null}
export PATH="$HOME/.local/bin:$PATH"
```
## Build Options
### Building Specific Components
The m87 workspace contains multiple packages. You can build specific components:
**Build only the CLI:**
```bash theme={null}
cargo build --release -p m87-client
```
**Build only shared libraries:**
```bash theme={null}
cargo build --release -p m87-shared
```
**Build server components (AGPL license):**
```bash theme={null}
cargo build --release -p m87-server
```
### Platform-Specific Builds
Build configuration is automatically detected based on your operating system:
* **Linux:** Full functionality (CLI + runtime)
* **macOS:** CLI only (runtime not available)
The m87 runtime only runs on Linux. macOS builds include the CLI commands but exclude runtime functionality.
### Development Build
For faster compilation during development:
```bash theme={null}
cargo build
```
Development builds:
* Compile faster (\~50% faster)
* Include debug symbols
* No optimizations (significantly slower at runtime)
* Located at `target/debug/m87`
Development builds are not suitable for production use due to reduced performance.
## Build Configuration
The build is configured via `Cargo.toml` in the workspace root.
### Release Profile Settings
The release profile is optimized for performance and minimal binary size:
```toml theme={null}
[profile.release]
opt-level = 3 # Maximum optimization
lto = "fat" # Full link-time optimization
codegen-units = 1 # Better optimization (slower compile)
strip = true # Strip debug symbols
```
These settings produce:
* Smaller binary size (\~40-60% reduction)
* Better runtime performance
* Longer compilation time
* No debug symbols (use `strip = false` if needed)
### Custom Build Profiles
Create a custom profile for specific needs:
```bash theme={null}
# Fast compile, some optimization
cargo build --profile dev-opt
```
Add to `Cargo.toml`:
```toml theme={null}
[profile.dev-opt]
inherits = "dev"
opt-level = 2
```
## Cross-Compilation
Build for different architectures using cross-compilation.
### Setup Cross-Compilation
Install the target architecture:
```bash theme={null}
# For ARM64 (aarch64)
rustup target add aarch64-unknown-linux-gnu
# For ARMv7 (32-bit ARM)
rustup target add armv7-unknown-linux-gnueabihf
# For x86_64 Linux (from macOS)
rustup target add x86_64-unknown-linux-gnu
```
### Build for Target Architecture
```bash theme={null}
# Build for ARM64
cargo build --release --target aarch64-unknown-linux-gnu
# Binary location
ls target/aarch64-unknown-linux-gnu/release/m87
```
### Using cross for Easy Cross-Compilation
For easier cross-compilation, use the `cross` tool:
```bash theme={null}
# Install cross
cargo install cross
# Build for ARM64 using Docker
cross build --release --target aarch64-unknown-linux-gnu
# Build for ARMv7
cross build --release --target armv7-unknown-linux-gnueabihf
```
`cross` uses Docker to provide a complete cross-compilation environment, eliminating the need for target-specific system dependencies.
## Building with Docker
Build m87 inside a Docker container for a consistent environment.
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
docker build -f m87-client/Dockerfile -t m87 .
```
```bash theme={null}
docker run -it --rm \
--user "$(id -u):$(id -g)" \
-v "$HOME/.config/m87:/.config/m87" \
-e HOME=/ \
m87 --version
```
Add to your `~/.bashrc` or `~/.zshrc`:
```bash theme={null}
alias m87='docker run -it --rm --user "$(id -u):$(id -g)" -v "$HOME/.config/m87:/.config/m87" -e HOME=/ m87'
```
Now use m87 as usual:
```bash theme={null}
m87 login
m87 devices list
```
## Optimizing Build Times
### Use Cargo Cache
Cargo caches dependencies. Keep them updated:
```bash theme={null}
cargo update
```
### Parallel Compilation
Increase parallel jobs (default is number of CPU cores):
```bash theme={null}
cargo build --release -j 8
```
### Use sccache
Distributed compilation cache:
```bash theme={null}
# Install sccache
cargo install sccache
# Configure
export RUSTC_WRAPPER=sccache
# Build
cargo build --release
# Check cache stats
sccache --show-stats
```
### Incremental Compilation
Enabled by default for dev builds, disabled for release. Enable for faster release builds:
```toml theme={null}
[profile.release]
incremental = true
```
Incremental compilation may produce slightly larger binaries.
## Verifying the Build
```bash theme={null}
ls -lh target/release/m87
```
Expected size: 10-30 MB depending on platform and optimization.
```bash theme={null}
./target/release/m87 --version
./target/release/m87 --help
```
```bash theme={null}
./target/release/m87 login
./target/release/m87 devices list
```
Compare behavior with the official release to ensure consistency.
## Development Workflow
For active development:
```bash theme={null}
# Watch for changes and rebuild automatically
cargo install cargo-watch
cargo watch -x 'build --release'
# Run tests
cargo test
# Run tests with output
cargo test -- --nocapture
# Check for issues without building
cargo check
# Format code
cargo fmt
# Lint with clippy
cargo clippy -- -D warnings
```
## Troubleshooting Build Issues
**Error:** `package requires rustc 1.85 or newer`
**Solution:**
```bash theme={null}
rustup update stable
rustc --version
```
**Error:** `could not find native static library`
**Solution:**
Install required system libraries:
```bash theme={null}
# Debian/Ubuntu
sudo apt-get install build-essential pkg-config libssl-dev
# Fedora/RHEL
sudo dnf install gcc pkg-config openssl-devel
# macOS
xcode-select --install
```
**Error:** `linking with 'cc' failed`
**Solution:**
Ensure you have a C compiler installed:
```bash theme={null}
# Check compiler
cc --version
gcc --version
# Install if missing
sudo apt-get install build-essential # Debian/Ubuntu
```
**Error:** `No space left on device`
**Solution:**
Cargo builds can use significant disk space. Clean old builds:
```bash theme={null}
cargo clean
rm -rf ~/.cargo/registry/cache
```
**Problem:** Build takes too long.
**Solutions:**
* Use development build instead: `cargo build` (no `--release`)
* Enable incremental compilation
* Use `sccache` for caching
* Increase parallel jobs: `cargo build -j 8`
## Next Steps
After building from source:
1. **Install the binary:** Copy to `~/.local/bin` or `/usr/local/bin`
2. **Set up development environment:** Run `m87 login` and connect a device
3. **Contribute:** Submit improvements via GitHub pull requests
## Contributing
Contributions are welcome! Before submitting:
```bash theme={null}
# Format code
cargo fmt
# Check for issues
cargo clippy
# Run tests
cargo test
# Build release binary
cargo build --release
```
See the [Contributing Guide](https://github.com/make87/m87/blob/main/CONTRIBUTING.md) for more details.
The m87-client and m87-shared packages are licensed under Apache-2.0. The m87-server package is licensed under AGPL-3.0-or-later.
# Troubleshooting
Source: https://docs.make87.com/advanced/troubleshooting
Common issues and solutions for m87 CLI and runtime
This guide covers common issues you may encounter when using m87 and their solutions.
## Authentication Issues
**Problem:** Running `m87 login` doesn't open a browser window.
**Solutions:**
Ensure you have a default browser configured on your system.
If the browser doesn't open automatically, copy the URL from the terminal and paste it into your browser manually.
Check if `BROWSER` environment variable is set correctly:
```bash theme={null}
echo $BROWSER
```
**Problem:** Commands fail with authentication errors.
**Solution:**
```bash theme={null}
m87 logout
m87 login
```
This clears expired credentials and re-authenticates with the platform.
**Problem:** Error message: "No credentials found"
**Solution:**
Credentials are stored in `~/.config/m87/`. If this directory is missing or corrupted:
```bash theme={null}
m87 login
```
On Linux, ensure `~/.config` directory has proper permissions (755 or 700).
## Runtime Issues
**Problem:** Device registered but never shows as "online".
**Solutions:**
List pending devices from your workstation:
```bash theme={null}
m87 devices list
```
```bash theme={null}
m87 devices approve
```
On the device, check if the runtime is actually running:
```bash theme={null}
m87 runtime status
```
**Problem:** `m87 runtime run` fails or exits immediately.
**Diagnostic steps:**
```bash theme={null}
# Check if another instance is running
ps aux | grep m87
# Check system logs
journalctl -u m87-runtime -n 50
# Verify network connectivity
ping make87.com
```
**Common causes:**
* Port conflict with another process
* Firewall blocking outbound connections
* Missing network connectivity
* Corrupted configuration file
**Solution:**
Try restarting with verbose logging:
```bash theme={null}
RUST_LOG=debug m87 runtime run
```
**Problem:** Runtime service doesn't start after running `m87 runtime enable --now`.
**Solutions:**
```bash theme={null}
m87 runtime status
# Or use systemd directly
systemctl --user status m87-runtime
```
```bash theme={null}
journalctl --user -u m87-runtime -n 100 --no-pager
```
Ensure m87 binary is in your PATH:
```bash theme={null}
which m87
```
```bash theme={null}
m87 runtime disable
m87 runtime enable --now
```
The runtime service runs as your user (not root). Ensure your user has necessary permissions.
## Connection Issues
**Problem:** Device appears in device list but `m87 shell` or other commands timeout.
**Solutions:**
```bash theme={null}
m87 status
```
Use another access method (physical access, serial, etc.) to check:
```bash theme={null}
m87 runtime status
```
Network interruptions can cause connection issues. Restart the runtime:
```bash theme={null}
m87 exec -- 'm87 runtime restart'
```
**Problem:** Shell sessions drop unexpectedly.
**Possible causes:**
* Unstable network connection on the device
* Device going to sleep or low-power mode
* High network latency
**Solutions:**
* Check device network stability
* Disable power management features that suspend network interfaces
* Use `m87 exec` for non-interactive commands instead
**Problem:** `m87 forward` command succeeds but port isn't accessible.
**Solutions:**
On the device, check if the service is running:
```bash theme={null}
m87 exec -- 'netstat -tulpn | grep '
```
Ensure the service binds to `0.0.0.0` or `127.0.0.1`, not a specific IP.
Try connecting locally on your workstation:
```bash theme={null}
curl http://localhost:
```
Verify correct syntax:
```bash theme={null}
m87 forward 8080 # localhost:8080 → device:8080
m87 forward 3000:8080 # localhost:3000 → device:8080
```
## Platform-Specific Issues
**Problem:** Runtime commands fail on macOS.
**Explanation:**
The m87 runtime (on-device daemon) only runs on Linux. macOS supports the CLI only.
**Supported:**
* `m87 login`, `m87 logout`
* `m87 devices list`, `m87 devices approve`
* `m87 shell`, `m87 exec`
* All device management commands
**Not supported on macOS:**
* `m87 runtime run`
* `m87 runtime enable/start/stop`
**Problem:** Permission errors when running runtime commands.
**Solutions:**
* Runtime service uses `sudo` for systemd operations automatically
* Binary should be executable: `chmod +x ~/.local/bin/m87`
* Config directory permissions: `chmod 700 ~/.config/m87`
Don't run the runtime itself as root. The service runs as your user account.
## Build and Installation Issues
**Problem:** `curl -fsSL https://get.make87.com | sh` fails.
**Solutions:**
```bash theme={null}
curl -I https://get.make87.com
```
Download from [releases page](https://github.com/make87/m87/releases) and install manually:
```bash theme={null}
wget https://github.com/make87/m87/releases/latest/download/m87-linux-amd64
chmod +x m87-linux-amd64
mv m87-linux-amd64 ~/.local/bin/m87
```
Ensure `~/.local/bin` is in your PATH:
```bash theme={null}
echo $PATH | grep ".local/bin"
```
**Problem:** Building from source fails with compilation errors.
**Solutions:**
m87 requires Rust 1.85+:
```bash theme={null}
rustc --version
rustup update
```
```bash theme={null}
cargo clean
cargo build --release
```
Ensure you have required system dependencies:
```bash theme={null}
# Debian/Ubuntu
sudo apt-get install build-essential pkg-config libssl-dev
# Fedora/RHEL
sudo dnf install gcc pkg-config openssl-devel
```
## Docker-Specific Issues
**Problem:** `m87 docker ps` returns permission errors.
**Solutions:**
* Ensure Docker is installed on the device
* Add user to docker group:
```bash theme={null}
m87 exec -- 'sudo usermod -aG docker $USER'
```
* Restart runtime after group change:
```bash theme={null}
m87 exec -- 'm87 runtime restart'
```
**Problem:** `m87 deploy` succeeds but containers don't start.
**Diagnostic steps:**
```bash theme={null}
# Check deployment status
m87 deployment status --logs
# Check Docker logs
m87 docker logs
# Verify compose file syntax
m87 deployment show --yaml
```
## Getting More Help
If your issue isn't covered here:
1. **Enable debug logging:**
```bash theme={null}
RUST_LOG=debug m87
```
2. **Check audit logs:**
```bash theme={null}
m87 audit --details
```
3. **View runtime logs:**
```bash theme={null}
m87 logs
```
4. **Report issues:**
File a bug report at [github.com/make87/m87/issues](https://github.com/make87/m87/issues)
When reporting issues, include:
* m87 version (`m87 --version`)
* Operating system and architecture
* Full error message
* Debug logs (with sensitive data redacted)
# Updating m87
Source: https://docs.make87.com/advanced/updating
Keep your m87 CLI and runtime up to date
Keeping m87 updated ensures you have the latest features, bug fixes, and security improvements.
## Update Command
The easiest way to update m87 is using the built-in update command:
```bash theme={null}
m87 update
```
This downloads and installs the latest m87 binary to the same location as your current installation.
The update command works for installations in standard locations like `~/.local/bin` or `/usr/local/bin`. For custom installations, you may need to update manually.
## Updating the CLI
First, check which version you're currently running:
```bash theme={null}
m87 --version
```
Update to the latest version:
```bash theme={null}
m87 update
```
The command will:
* Check for the latest release
* Download the appropriate binary for your platform
* Replace your current m87 binary
* Preserve file permissions
Confirm the new version is installed:
```bash theme={null}
m87 --version
```
## Updating the Runtime
After updating the m87 CLI, you should also update the runtime on your edge devices.
### Update Local Runtime
If you're updating the runtime on the same machine where you updated the CLI:
```bash theme={null}
m87 runtime restart
```
This restarts the runtime service with the new binary.
Restarting the runtime will briefly disconnect any active sessions to the device.
### Update Remote Device Runtime
To update the runtime on a remote device:
Execute the update command on the remote device:
```bash theme={null}
m87 exec -it -- 'm87 update'
```
Restart the runtime to use the new binary:
```bash theme={null}
m87 exec -it -- 'm87 runtime restart'
```
Check the runtime is running with the new version:
```bash theme={null}
m87 exec -- 'm87 --version'
```
### Combined Update Command
You can combine both steps into a single command:
```bash theme={null}
m87 exec -it -- 'm87 update && m87 runtime restart'
```
## Update Multiple Devices
To update multiple devices at once, use a shell loop:
```bash theme={null}
for device in device1 device2 device3; do
echo "Updating $device..."
m87 $device exec -it -- 'm87 update && m87 runtime restart'
done
```
Or list all devices and update them:
```bash theme={null}
m87 devices list --format json | jq -r '.[] | .name' | while read device; do
echo "Updating $device..."
m87 $device exec -it -- 'm87 update && m87 runtime restart' || echo "Failed to update $device"
done
```
## Manual Update Methods
### Update via Installation Script
Re-run the installation script to get the latest version:
```bash theme={null}
curl -fsSL https://get.make87.com | sh
```
This will download and install the latest release to `~/.local/bin/m87`.
### Update from GitHub Releases
Visit the [releases page](https://github.com/make87/m87/releases) and download the appropriate binary for your platform:
**Linux (amd64):**
```bash theme={null}
wget https://github.com/make87/m87/releases/latest/download/m87-linux-amd64
```
**Linux (arm64):**
```bash theme={null}
wget https://github.com/make87/m87/releases/latest/download/m87-linux-arm64
```
**macOS (amd64):**
```bash theme={null}
wget https://github.com/make87/m87/releases/latest/download/m87-darwin-amd64
```
**macOS (arm64):**
```bash theme={null}
wget https://github.com/make87/m87/releases/latest/download/m87-darwin-arm64
```
```bash theme={null}
chmod +x m87-*
```
```bash theme={null}
# Find current location
which m87
# Replace it (example for ~/.local/bin)
mv m87-linux-amd64 ~/.local/bin/m87
```
### Update from Source
If you built m87 from source, update by pulling the latest code and rebuilding:
```bash theme={null}
cd /path/to/m87
git pull origin main
cargo build --release
cp target/release/m87 ~/.local/bin/
```
See [Building from Source](/advanced/building-from-source) for detailed build instructions.
## Docker-Based Installation
If you're running m87 via Docker:
```bash theme={null}
cd /path/to/m87-repo
git pull origin main
docker build -f m87-client/Dockerfile -t m87 .
```
```bash theme={null}
docker run --rm m87 --version
```
Your alias remains the same, so no additional configuration is needed.
## Update Strategies
### Scheduled Updates
For production deployments, consider scheduling updates during maintenance windows:
```bash theme={null}
# Cron job to update at 3 AM every Sunday
0 3 * * 0 /home/user/.local/bin/m87 update && /home/user/.local/bin/m87 runtime restart
```
### Staged Rollout
For large fleets, update a small subset first:
```bash theme={null}
m87 staging-device exec -it -- 'm87 update && m87 runtime restart'
```
```bash theme={null}
m87 staging-device status
m87 staging-device logs
```
Once verified, update production devices in batches.
## Version Compatibility
### CLI and Runtime Versions
The m87 CLI and runtime are designed to be compatible across versions, but it's recommended to keep them in sync:
* **Recommended:** CLI and runtime on the same version
* **Supported:** CLI up to 2 minor versions ahead of runtime
* **Not recommended:** Runtime newer than CLI
### Checking Versions
**Local CLI version:**
```bash theme={null}
m87 --version
```
**Remote runtime version:**
```bash theme={null}
m87 exec -- 'm87 --version'
```
## Rollback
If an update causes issues, you can rollback to a previous version:
Visit [releases page](https://github.com/make87/m87/releases) and download a specific version:
```bash theme={null}
wget https://github.com/make87/m87/releases/download/v0.x.x/m87-linux-amd64
```
```bash theme={null}
chmod +x m87-linux-amd64
mv m87-linux-amd64 ~/.local/bin/m87
```
```bash theme={null}
m87 runtime restart
```
## Troubleshooting Updates
**Problem:** `m87 update` returns an error.
**Solutions:**
* Check network connectivity: `curl -I https://github.com`
* Verify write permissions to the installation directory
* Try manual update from GitHub releases
* Check disk space: `df -h`
**Problem:** Runtime fails to start after updating.
**Solutions:**
```bash theme={null}
# Check runtime status
m87 runtime status
# View logs
journalctl --user -u m87-runtime -n 50
# Try manual restart
m87 runtime stop
m87 runtime start
```
If issues persist, see [Troubleshooting](/advanced/troubleshooting) guide.
**Problem:** `m87 --version` still shows old version.
**Possible causes:**
* Multiple m87 binaries in PATH
* Shell cached the old binary location
**Solutions:**
```bash theme={null}
# Clear shell hash
hash -r
# Find all m87 binaries
which -a m87
# Use full path
~/.local/bin/m87 --version
```
## Release Notifications
To stay informed about new releases:
1. **Watch the GitHub repository:**\
Visit [github.com/make87/m87](https://github.com/make87/m87) and click "Watch" → "Custom" → "Releases"
2. **Check for updates periodically:**
```bash theme={null}
m87 update --check
```
3. **Subscribe to release RSS:**\
`https://github.com/make87/m87/releases.atom`
Major version updates may include breaking changes. Always review the release notes before updating production systems.
# CLI Reference
Source: https://docs.make87.com/api/cli-reference
Complete command reference for the m87 CLI
## Global Flags
Enable verbose logging output
## Top-Level Commands
### Authentication
#### `m87 login`
Authenticate with make87 via browser-based OAuth flow.
```bash theme={null}
m87 login
```
#### `m87 logout`
Logout and clear local credentials.
```bash theme={null}
m87 logout
```
### Device Management
#### `m87 devices list`
List all accessible devices.
```bash theme={null}
m87 devices list
```
#### `m87 devices approve `
Approve a pending device registration.
Device name or ID to approve
```bash theme={null}
m87 devices approve my-device
```
#### `m87 devices reject `
Reject a pending device registration.
Device name or ID to reject
```bash theme={null}
m87 devices reject my-device
```
#### `m87 devices show `
Show detailed information about a specific device.
Device name or ID
```bash theme={null}
m87 devices show my-device
```
This command is not yet fully implemented.
### File Operations
#### `m87 cp `
Copy files between local and remote devices (SCP-style).
Source path. Use `:` for remote, `` for local.
Destination path. Use `:` for remote, `` for local.
```bash theme={null}
# Copy from device to local
m87 cp my-device:/var/log/app.log ./app.log
# Copy from local to device
m87 cp ./config.yml my-device:/etc/app/config.yml
```
#### `m87 sync `
Sync files between local and remote devices (rsync-style).
Source path. Use `:` for remote, `` for local.
Destination path. Use `:` for remote, `` for local.
Delete files from destination that are not present in source
Watch for changes and sync automatically
Show what would be done without making changes
Exclude files matching pattern (can be used multiple times)
```bash theme={null}
# Sync directory to device
m87 sync ./src my-device:/app/src
# Sync with delete flag
m87 sync --delete ./src my-device:/app/src
# Watch mode with exclusions
m87 sync --watch --exclude "*.log" --exclude "node_modules" ./src my-device:/app/src
```
#### `m87 ls `
List files on a device.
Remote path in format `:`
```bash theme={null}
m87 ls my-device:/var/log
```
### Utility Commands
#### `m87 version`
Show CLI version information including build details and platform.
```bash theme={null}
m87 version
```
#### `m87 update`
Update the CLI to the latest version.
```bash theme={null}
m87 update
```
### Configuration
#### `m87 config set`
Set configuration values.
Override API URL (e.g., [https://eu.public.make87.dev](https://eu.public.make87.dev))
Set owner reference (email or org ID)
Set make87 API URL
Set make87 app URL
Trust invalid server certificates (use with caution)
```bash theme={null}
m87 config set --runtime-server-url https://eu.public.make87.dev
m87 config set --owner-reference myorg
```
#### `m87 config show`
Display current configuration.
```bash theme={null}
m87 config show
```
#### `m87 config file`
Show path to configuration file.
```bash theme={null}
m87 config file
```
### SSH Commands
#### `m87 ssh enable`
Enable SSH host resolution for `.m87` domains.
```bash theme={null}
m87 ssh enable
```
After enabling, you can use standard SSH:
```bash theme={null}
ssh my-device.m87
```
#### `m87 ssh disable`
Disable SSH host resolution.
```bash theme={null}
m87 ssh disable
```
### MCP Server
#### `m87 mcp`
Start MCP server (Model Context Protocol) for AI agent integration. The server runs on stdin/stdout and exposes m87 platform commands as MCP tools.
```bash theme={null}
m87 mcp
```
See [MCP Overview](/api/mcp-overview) for configuration details.
## Device Commands
Device commands follow the pattern `m87 `. These commands operate on a specific device.
### `m87 shell`
Open an interactive shell on the device.
```bash theme={null}
m87 my-device shell
```
### `m87 exec`
Execute a command on the device.
Keep stdin open (for responding to prompts)
Allocate a pseudo-TTY (for TUI apps like vim, htop)
Command and arguments to execute (use `--` before command)
```bash theme={null}
# Simple command
m87 my-device exec -- ls -la
# Interactive command
m87 my-device exec -it -- vim config.yml
# Command with pipes
m87 my-device exec -- "ps aux | grep nginx"
```
### `m87 forward`
Forward remote port(s) to localhost.
Port forwarding specifications. Supports:
* Single port: `8080`
* Port mapping: `3000:8080` (local:remote)
* Port range: `8080-8090`
* Range mapping: `8080-8090:9080-9090`
* With host: `8080:192.168.1.50:9080`
* With protocol: `8080/udp` (default: tcp)
```bash theme={null}
# Forward single port
m87 my-device forward 8080
# Forward to different local port
m87 my-device forward 3000:8080
# Forward multiple ports
m87 my-device forward 8080 9090 3000
# Forward port range
m87 my-device forward 8080-8090
# Forward to specific host on device network
m87 my-device forward 8080:192.168.1.50:9080
# UDP forwarding
m87 my-device forward 8080/udp
```
### `m87 docker`
Run docker commands on the device.
Docker CLI arguments (passed through to docker command)
```bash theme={null}
# List containers
m87 my-device docker ps
# List all containers
m87 my-device docker ps -a
# Run a container
m87 my-device docker run -d nginx
# Execute in container
m87 my-device docker exec -it my-container bash
# View logs
m87 my-device docker logs my-container
```
### `m87 logs`
Stream container logs from the device.
Follow log output
Number of lines to show from end of logs
```bash theme={null}
# View last 100 lines
m87 my-device logs
# Follow logs
m87 my-device logs -f
# Show last 50 lines
m87 my-device logs --tail 50
```
### `m87 metrics`
Show device system metrics (CPU, memory, disk, network).
```bash theme={null}
m87 my-device metrics
```
Alias: `m87 stats`
### `m87 status`
Show device status including health, crashes, and incidents.
```bash theme={null}
m87 my-device status
```
### `m87 audit`
View audit logs showing who interacted with the device.
End time in RFC 3339 format (e.g., 2026-01-31 or 2026-01-31T13:00:00)
Start time in RFC 3339 format
Maximum number of log entries
Show detailed audit information
```bash theme={null}
# Recent audit logs
m87 my-device audit
# Last 50 entries with details
m87 my-device audit --max 50 --details
# Time range query
m87 my-device audit --since 2026-01-01 --until 2026-01-31
```
### `m87 serial`
Connect to a serial device.
Path to serial device (e.g., /dev/ttyUSB0)
Baud rate for serial connection
```bash theme={null}
# Connect with default baud rate
m87 my-device serial /dev/ttyUSB0
# Connect with custom baud rate
m87 my-device serial /dev/ttyUSB0 9600
```
## Deployment Commands
Deployment commands manage asynchronous job execution on devices.
### `m87 deploy`
Add a run spec to a deployment.
Path to deployment file (docker-compose.yml or run spec YAML)
Spec type: `auto`, `compose`, `runspec`, `deployment`
Optional display name for the run spec
Add to a specific deployment (otherwise uses active deployment)
```bash theme={null}
# Deploy docker-compose file
m87 my-device deploy ./docker-compose.yml
# Deploy with custom name
m87 my-device deploy ./my-app.yml --name "production-app"
# Deploy to specific deployment
m87 my-device deploy ./app.yml --deployment-id dep-123
# Force spec type
m87 my-device deploy ./custom.yml --type runspec
```
### `m87 undeploy`
Remove a run spec from a deployment.
Job ID or name to remove
Remove from specific deployment (otherwise uses active deployment)
```bash theme={null}
m87 my-device undeploy my-app
m87 my-device undeploy my-app --deployment-id dep-123
```
### `m87 deployment list`
List all deployments for the device.
```bash theme={null}
m87 my-device deployment list
```
### `m87 deployment new`
Create a new deployment.
Make this deployment active immediately
```bash theme={null}
# Create inactive deployment
m87 my-device deployment new
# Create and activate
m87 my-device deployment new --active
```
### `m87 deployment show`
Show deployment details including run specs.
Specific deployment to show (defaults to active deployment)
Output as YAML instead of formatted display
```bash theme={null}
# Show active deployment
m87 my-device deployment show
# Show specific deployment
m87 my-device deployment show --deployment-id dep-123
# Show as YAML
m87 my-device deployment show --yaml
```
### `m87 deployment status`
Get deployment execution status.
Specific deployment (defaults to active deployment)
Show logs from deployment steps
```bash theme={null}
# Status of active deployment
m87 my-device deployment status
# Status with logs
m87 my-device deployment status --logs
# Status of specific deployment
m87 my-device deployment status --deployment-id dep-123
```
### `m87 deployment active`
Print the currently active deployment ID.
```bash theme={null}
m87 my-device deployment active
```
### `m87 deployment activate`
Set the active deployment.
Deployment ID to activate
```bash theme={null}
m87 my-device deployment activate dep-123
```
### `m87 deployment rm`
Remove a deployment.
Deployment ID to remove
Skip confirmation prompt
```bash theme={null}
m87 my-device deployment rm dep-123
m87 my-device deployment rm dep-123 --force
```
### `m87 deployment clone`
Clone an existing deployment into a new one.
Source deployment ID to clone
Make the cloned deployment active immediately
```bash theme={null}
m87 my-device deployment clone dep-123
m87 my-device deployment clone dep-123 --active
```
### `m87 deployment update`
Update a deployment (remove/replace/rename specs).
Deployment to update (defaults to active)
Remove job(s) by ID (can be used multiple times)
Replace job: `=` (can be used multiple times)
Rename job: `=` (can be used multiple times)
Enable job(s) by ID (can be used multiple times)
Disable job(s) by ID (can be used multiple times)
Spec type for replacements: `auto`, `compose`, `runspec`
```bash theme={null}
# Remove a job
m87 my-device deployment update --rm old-app
# Replace a job
m87 my-device deployment update --replace my-app=./new-version.yml
# Rename a job
m87 my-device deployment update --rename my-app=production-app
# Enable/disable jobs
m87 my-device deployment update --enable app1 --disable app2
# Combine operations
m87 my-device deployment update --rm old-app --replace app=./new.yml --enable new-app
```
## Device Access Control
### `m87 access list`
List users with access to the device.
```bash theme={null}
m87 my-device access list
```
### `m87 access add`
Grant access to a user or organization.
Email address or organization ID
Role: `admin`, `editor`, or `viewer`
```bash theme={null}
m87 my-device access add user@example.com editor
m87 my-device access add my-org admin
```
### `m87 access remove`
Revoke access from a user or organization.
Email address or organization ID
```bash theme={null}
m87 my-device access remove user@example.com
```
### `m87 access update`
Update user or organization role.
Email address or organization ID
New role: `admin`, `editor`, or `viewer`
```bash theme={null}
m87 my-device access update user@example.com admin
```
## Organization Commands
### `m87 org list`
List all organizations.
```bash theme={null}
m87 org list
```
### `m87 org create`
Create a new organization.
Organization ID
Email of organization owner
```bash theme={null}
m87 org create my-org owner@example.com
```
### `m87 org delete`
Delete an organization.
Organization ID to delete
```bash theme={null}
m87 org delete my-org
```
### `m87 org update`
Update organization ID.
Current organization ID
New organization ID
```bash theme={null}
m87 org update my-org new-org-name
```
### Organization Members
#### `m87 org members list`
List organization members.
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org members list
m87 org members list --org-id my-org
```
#### `m87 org members add`
Add a member to the organization.
Member email address
Role: `admin`, `editor`, or `viewer`
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org members add user@example.com editor
m87 org members add admin@example.com admin --org-id my-org
```
#### `m87 org members update`
Update a member's role.
Member email address
New role: `admin`, `editor`, or `viewer`
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org members update user@example.com admin
```
#### `m87 org members remove`
Remove a member from the organization.
Member email address
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org members remove user@example.com
```
### Organization Devices
#### `m87 org devices list`
List devices owned by the organization.
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org devices list
m87 org devices list --org-id my-org
```
#### `m87 org devices add`
Add a device to the organization.
Device name or ID
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org devices add my-device
m87 org devices add my-device --org-id my-org
```
#### `m87 org devices remove`
Remove a device from the organization.
Device name or ID
Organization ID (auto-resolved if omitted)
```bash theme={null}
m87 org devices remove my-device
m87 org devices remove my-device --org-id my-org
```
## Runtime Commands (Linux Only)
These commands manage the m87 runtime service on Linux devices. Most require root privileges and automatically invoke `sudo`.
### `m87 runtime login`
Register this device as a runtime (headless flow, requires approval).
Organization ID to register runtime under (mutually exclusive with --email)
Email address to register runtime under (mutually exclusive with --org-id)
```bash theme={null}
m87 runtime login --email you@example.com
m87 runtime login --org-id my-org
```
After registration, approve from your workstation with `m87 devices approve `.
### `m87 runtime logout`
Logout and deauthenticate the runtime.
```bash theme={null}
m87 runtime logout
```
### `m87 runtime run`
Run the runtime daemon (blocking, used by systemd service).
Organization ID to register runtime under
Email address to register runtime under
```bash theme={null}
m87 runtime run --email you@example.com
```
### `m87 runtime start`
Start the runtime service now.
Organization ID to register runtime under
Email address to register runtime under
```bash theme={null}
sudo m87 runtime start
```
### `m87 runtime stop`
Stop the runtime service now.
```bash theme={null}
sudo m87 runtime stop
```
### `m87 runtime restart`
Restart the runtime service.
Organization ID to register runtime under
Email address to register runtime under
```bash theme={null}
sudo m87 runtime restart
```
### `m87 runtime enable`
Configure service to auto-start on boot.
Enable AND start service immediately
Organization ID to register runtime under
Email address to register runtime under
```bash theme={null}
sudo m87 runtime enable
sudo m87 runtime enable --now
```
### `m87 runtime disable`
Remove auto-start on boot.
Disable AND stop service immediately
```bash theme={null}
sudo m87 runtime disable
sudo m87 runtime disable --now
```
### `m87 runtime status`
Show local runtime service status.
```bash theme={null}
m87 runtime status
```
# Environment Variables
Source: https://docs.make87.com/api/environment-variables
Environment variables for m87 server configuration
These environment variables configure the m87 server. They are typically set in a `.env` file in the server directory.
## Database Configuration
MongoDB connection string. Usually points to the Mongo service name inside docker-compose.
**Example:** `mongodb://mongo:27017`
Logical database name used by m87-server.
MongoDB root username. Only required for secured Mongo setups.
MongoDB root password. Only required for secured Mongo setups.
## Authentication & OAuth
OAuth/OIDC issuer URL used to validate access tokens.
**Example:** `https://auth.make87.com/`
Expected OAuth audience for access tokens. Must match the `aud` claim issued by the auth provider.
**Example:** `https://auth.make87.com`
## Server Networking
Public base address under which this server is reachable. Used to check the SNI of incoming requests for device ID prefixes.
**Example:** `your.public.domain`
Port for the unified public interface (typically proxied). Needs to match the port mapped to 443 for QUIC endpoints.
Port for the REST API (may be internal or separately exposed). Used for the WebTransport endpoint for the web app. Mapped to 8080.
## Environment Flags
Whether the server runs in staging mode.
* `0` = production behavior
* `1` = staging / relaxed checks / verbose logging
Whether newly registered users require manual approval.
* `true` = user accounts start inactive until approved
* `false` = users are active immediately
Whether devices can be shared across users of different organizations.
* `true` = cross-org device sharing allowed
* `false` = devices are restricted to their org
Domains that are auto-approved on signup. If a user's email domain matches one of these, approval is skipped.
Comma-separated list with no spaces.
**Example:** `make87.com,example.org`
## Admin & Security
Static admin API key used for privileged actions such as:
* Approving users
* Creating organizations
* Bootstrapping admin access
**Default:** `change-me` (must be changed in production)
List of email addresses that should automatically receive admin privileges.
Comma-separated list with no spaces.
**Example:** `admin@org.com,admin@example.org`
## Data Retention
Number of days audit log entries are retained. Older entries are automatically deleted.
Number of days deployment/report data is retained. Older reports are automatically deleted.
## Example Configuration
```bash .env theme={null}
# Database
MONGO_URI=mongodb://mongo:27017
MONGO_DB=m87-server
# Auth
OAUTH_ISSUER=https://auth.make87.com/
OAUTH_AUDIENCE=https://auth.make87.com
# Networking
PUBLIC_ADDRESS=api.example.com
UNIFIED_PORT=8084
REST_PORT=8085
# Environment
STAGING=0
USERS_NEED_APPROVAL=false
ALLOW_CROSS_ORG_DEVICE_SHARING=false
USER_AUTO_ACCEPT_DOMAINS=example.com,trusted.org
# Security
ADMIN_KEY=your-secret-admin-key
ADMIN_EMAILS=admin@example.com
# Retention
AUDIT_RETENTION_DAYS=30
REPORT_RETENTION_DAYS=7
```
## Security Best Practices
Always change the default `ADMIN_KEY` from `change-me` to a strong, randomly generated value in production environments.
Store sensitive environment variables (like `ADMIN_KEY` and database credentials) in a secure secrets manager rather than committing them to version control.
The `.env.example` file in the repository provides a template with all available options and their descriptions.
# MCP Server Overview
Source: https://docs.make87.com/api/mcp-overview
Model Context Protocol integration for AI agent access
The m87 CLI includes a built-in [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that exposes all platform commands as tools for AI agents.
## What is MCP?
MCP (Model Context Protocol) is a standard protocol for connecting AI assistants to external tools and data sources. The m87 MCP server allows AI agents to programmatically interact with your devices, deployments, and infrastructure.
## Quick Start
Start the MCP server:
```bash theme={null}
m87 mcp
```
The server runs on stdin/stdout and communicates via the MCP protocol.
## Configuration
### Claude Desktop
Add to your Claude Desktop configuration (`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
```json theme={null}
{
"mcpServers": {
"m87": {
"command": "m87",
"args": ["mcp"]
}
}
}
```
If `m87` is not in your `PATH`, use an absolute path:
```json theme={null}
{
"mcpServers": {
"m87": {
"command": "/usr/local/bin/m87",
"args": ["mcp"]
}
}
}
```
### Claude Code
Add to your MCP settings:
```json theme={null}
{
"m87": {
"type": "stdio",
"command": "m87",
"args": ["mcp"]
}
}
```
### Other MCP Clients
Any MCP-compatible client can connect to the m87 server using the stdio transport:
```json theme={null}
{
"type": "stdio",
"command": "m87",
"args": ["mcp"]
}
```
## Server Information
`m87-mcp`
Matches m87 CLI version
* Tools: Yes
* Prompts: No
* Resources: No
## Available Tools
The MCP server exposes the following categories of tools:
### Device Management
* `devices_list` - List all accessible devices
* `devices_approve` - Approve pending device registrations
* `devices_reject` - Reject pending device registrations
* `device_status` - Get device health and status (supports batch)
* `device_audit_logs` - Get device audit logs (supports batch)
### Device Access Control
* `device_access_list` - List users with device access
* `device_access_add` - Grant device access
* `device_access_remove` - Revoke device access
### File Operations
* `device_ls` - List files on device
* `device_cp` - Copy files between local and remote
* `device_sync` - Sync files (rsync-style)
### Remote Execution
* `device_exec` - Execute commands on devices (supports batch)
* `docker_exec` - Run Docker commands on devices
### Port Forwarding
* `forward_start` - Start persistent port forwarding session
* `forward_stop` - Stop forwarding session
* `forward_list` - List active forwarding sessions
### Deployments
* `device_deploy` - Deploy specs to devices
* `device_undeploy` - Remove deployment specs
* `device_deployment_list` - List deployments (supports batch)
* `device_deployment_new` - Create new deployment
* `device_deployment_show` - Show deployment details
* `device_deployment_status` - Get deployment status (supports batch)
* `device_deployment_active` - Get active deployment ID
* `device_deployment_activate` - Set active deployment
* `device_deployment_rm` - Remove deployment
* `device_deployment_clone` - Clone deployment
### Organization Management
* `org_list` - List organizations
* `org_create` - Create organization
* `org_delete` - Delete organization
* `org_update` - Update organization
* `org_members_list` - List organization members
* `org_members_add` - Add organization member
* `org_members_remove` - Remove organization member
* `org_devices_list` - List organization devices
* `org_devices_add` - Add device to organization
* `org_devices_remove` - Remove device from organization
See [MCP Tools Reference](/api/mcp-tools) for detailed tool documentation.
## Batch Operations
Many tools support batch operations for efficiency. Instead of passing a single `device` parameter, you can pass a `devices` array:
```json theme={null}
// Single device
{
"device": "my-device"
}
// Multiple devices (batch)
{
"devices": ["device-1", "device-2", "device-3"]
}
```
Batch-supported tools:
* `device_status`
* `device_audit_logs`
* `device_exec`
* `device_deployment_list`
* `device_deployment_status`
Batch operations run in parallel and return an array of results:
```json theme={null}
{
"results": [
{"device": "device-1", "status": "online", ...},
{"device": "device-2", "status": "offline", ...},
{"device": "device-3", "error": "Not found"}
]
}
```
## Authentication
The MCP server uses your existing m87 CLI credentials. Ensure you're logged in before starting the server:
```bash theme={null}
m87 login
m87 mcp
```
## Error Handling
The MCP server returns errors in two ways:
1. **MCP-level errors**: Invalid requests, missing parameters, etc.
2. **Operation errors**: Returned as data in the response (e.g., command failures)
### Example: Command with Non-Zero Exit
```json theme={null}
// device_exec returns exit codes as data, not errors
{
"output": "command not found: invalid-cmd",
"exit_code": 127
}
```
This allows agents to handle failures gracefully.
## Output Formatting
All tool responses:
* Return JSON-formatted data
* Strip ANSI color codes (not useful for AI agents)
* Include relevant context (device names, IDs, etc.)
## Session Management
The MCP server supports long-running operations:
### Port Forwarding Sessions
Forwarding runs in the background and persists across multiple tool calls:
```python theme={null}
# Start forwarding
result = call_tool("forward_start", {
"device": "my-device",
"specs": ["8080:80", "3000:3000"]
})
session_id = result["session_id"]
# Do other work while forwarding is active
# ...
# Stop when done
call_tool("forward_stop", {"session_id": session_id})
```
### Docker Containers
For long-running containers, use the `-d` (detached) flag:
```python theme={null}
result = call_tool("docker_exec", {
"device": "my-device",
"args": ["run", "-d", "--name", "nginx", "nginx"],
"timeout_secs": 60
})
container_id = result["stdout"].strip()
```
## Best Practices
Use batch operations when querying multiple devices to reduce round-trip time.
Set appropriate `timeout_secs` for long-running operations like Docker builds or large file transfers.
The MCP server has full access to all devices you can access via the CLI. Ensure proper authentication and access controls.
The server automatically handles device name resolution, authentication, and connection management.
## Example Use Cases
### Fleet Status Check
```python theme={null}
# Check status of all production devices
result = call_tool("device_status", {
"devices": ["prod-1", "prod-2", "prod-3"]
})
```
### Deploy to Multiple Devices
```python theme={null}
devices = ["web-1", "web-2", "web-3"]
for device in devices:
call_tool("device_deploy", {
"device": device,
"file": "./docker-compose.yml",
"name": "web-app"
})
```
### Collect Logs from Fleet
```python theme={null}
result = call_tool("device_exec", {
"devices": ["app-1", "app-2", "app-3"],
"command": ["journalctl", "-u", "app", "--since", "1h"]
})
for r in result["results"]:
print(f"{r['device']}: {r['output']}")
```
## Troubleshooting
### Server Won't Start
Ensure you're logged in:
```bash theme={null}
m87 login
m87 mcp
```
### Tools Not Appearing
Check MCP client logs. The server should advertise all tools on initialization.
### Authentication Errors
Token may have expired. Re-login:
```bash theme={null}
m87 logout
m87 login
```
## Version Compatibility
The MCP server is available in m87 CLI version 0.1.0 and later. Check your version:
```bash theme={null}
m87 version
```
# MCP Tools Reference
Source: https://docs.make87.com/api/mcp-tools
Complete reference for all MCP tools available in the m87 server
This page documents all tools exposed by the m87 MCP server. Tools are organized by category.
Tools marked with **\[Batch]** support batch operations - pass `devices` array instead of `device` to operate on multiple devices at once.
## Device Management
### devices\_list
List all accessible devices.
**Parameters:** None
**Returns:**
```json theme={null}
[
{
"id": "dev-abc123",
"name": "my-device",
"status": "online",
"owner": "user@example.com"
}
]
```
### devices\_approve
Approve a pending device registration.
**Parameters:**
Device ID to approve
**Returns:**
```json theme={null}
{"status": "approved"}
```
### devices\_reject
Reject a pending device registration.
**Parameters:**
Device ID to reject
**Returns:**
```json theme={null}
{"status": "rejected"}
```
### device\_status **\[Batch]**
Get device status and health. Supports batch operations.
**Parameters:**
Single device name or ID (mutually exclusive with `devices`)
Multiple device names/IDs for batch execution (mutually exclusive with `device`)
**Returns (single):**
```json theme={null}
{
"status": "online",
"health": "healthy",
"uptime": 86400,
"last_seen": "2026-03-03T10:00:00Z"
}
```
**Returns (batch):**
```json theme={null}
{
"results": [
{"device": "device-1", "status": "online", ...},
{"device": "device-2", "status": "offline", ...}
]
}
```
### device\_audit\_logs **\[Batch]**
Get audit logs for a device. Supports batch operations.
**Parameters:**
Single device name or ID (mutually exclusive with `devices`)
Multiple device names/IDs (mutually exclusive with `device`)
Start time in ISO 8601 format (e.g., "2026-03-01T00:00:00Z")
End time in ISO 8601 format
Maximum number of logs to return
**Returns (single):**
```json theme={null}
{
"logs": [
{
"timestamp": "2026-03-03T10:00:00Z",
"user": "user@example.com",
"action": "shell",
"details": "..."
}
]
}
```
**Returns (batch):**
```json theme={null}
{
"results": [
{"device": "device-1", "logs": [...]},
{"device": "device-2", "logs": [...]}
]
}
```
## Device Access Control
### device\_access\_list
List users with access to a device.
**Parameters:**
Device name or ID
**Returns:**
```json theme={null}
[
{
"email": "user@example.com",
"role": "editor"
},
{
"org_id": "my-org",
"role": "admin"
}
]
```
### device\_access\_add
Grant access to a device.
**Parameters:**
Device name or ID
Email address or organization ID
Role: `admin`, `editor`, or `viewer`
**Returns:**
```json theme={null}
{"status": "added"}
```
### device\_access\_remove
Revoke access to a device.
**Parameters:**
Device name or ID
Email address or organization ID
**Returns:**
```json theme={null}
{"status": "removed"}
```
## File Operations
### device\_ls
List files in a device directory.
**Parameters:**
Remote path in format `:`
**Returns:**
```json theme={null}
[
{"name": "file.txt", "is_dir": false},
{"name": "logs", "is_dir": true}
]
```
### device\_cp
Copy files between local and remote device.
**Parameters:**
Source path. Use `:` for remote, `` for local.
Destination path. Use `:` for remote, `` for local.
**Returns:**
```json theme={null}
{"status": "copied"}
```
### device\_sync
Sync files between local and remote device.
**Parameters:**
Source path
Destination path
Delete files not in source
Show what would be done without making changes
File patterns to exclude
**Returns:**
```json theme={null}
{"status": "synced"}
```
## Remote Execution
### device\_exec **\[Batch]**
Execute a command on a device and return output. Non-zero exit codes are returned as data, not errors. Supports batch operations.
**Parameters:**
Single device name or ID (mutually exclusive with `devices`)
Multiple device names/IDs (mutually exclusive with `device`)
Command and arguments to execute
Command timeout in seconds
**Returns (single):**
```json theme={null}
{
"output": "command output here",
"exit_code": 0
}
```
**Returns (batch):**
```json theme={null}
{
"results": [
{"device": "device-1", "output": "...", "exit_code": 0},
{"device": "device-2", "error": "timeout"}
]
}
```
### docker\_exec
Run a docker command on a device and capture output. The docker socket is forwarded via QUIC automatically. For long-running containers use the `-d` flag.
**Parameters:**
Device name or ID
Docker CLI arguments (e.g., `["ps", "-a"]` or `["run", "-d", "nginx"]`)
Timeout in seconds (use higher values for builds/pulls)
**Returns:**
```json theme={null}
{
"stdout": "CONTAINER ID IMAGE ...",
"stderr": "",
"exit_code": 0
}
```
## Port Forwarding
### forward\_start
Start port/socket forwarding to a device. Returns a session ID for lifecycle management. Forwarding runs in the background until stopped.
**Parameters:**
Device name or ID
Forward specifications (e.g., `["8080:80", "/tmp/sock:/var/run/docker.sock"]`)
**Returns:**
```json theme={null}
{
"session_id": "1",
"device": "my-device",
"targets": ["TcpPort(8080->80)"],
"status": "started"
}
```
### forward\_stop
Stop a running forward session by session ID.
**Parameters:**
Session ID returned by `forward_start`
**Returns:**
```json theme={null}
{
"session_id": "1",
"status": "stopped"
}
```
### forward\_list
List all active forward sessions.
**Parameters:** None
**Returns:**
```json theme={null}
[
{
"session_id": "1",
"device": "my-device",
"specs": ["8080:80"],
"targets": ["TcpPort(8080->80)"]
}
]
```
## Deployment Operations
### device\_deploy
Add a deployment spec to a device.
**Parameters:**
Device name or ID
Path to deployment file (docker-compose.yml or run spec YAML)
Spec type: `auto`, `compose`, `runspec`, or `deployment`
Optional display name for the run spec
Target deployment ID (uses active if omitted)
**Returns:**
```json theme={null}
{"status": "deployed"}
```
### device\_undeploy
Remove a deployment spec from a device.
**Parameters:**
Device name or ID
Job ID to remove
Target deployment ID (uses active if omitted)
**Returns:**
```json theme={null}
{"status": "undeployed"}
```
### device\_deployment\_list **\[Batch]**
List all deployments for a device. Supports batch operations.
**Parameters:**
Single device name or ID (mutually exclusive with `devices`)
Multiple device names/IDs (mutually exclusive with `device`)
**Returns (single):**
```json theme={null}
{
"deployments": [
{
"id": "dep-123",
"active": true,
"jobs": [...]
}
]
}
```
**Returns (batch):**
```json theme={null}
{
"results": [
{"device": "device-1", "deployments": [...]},
{"device": "device-2", "deployments": [...]}
]
}
```
### device\_deployment\_new
Create a new deployment for a device.
**Parameters:**
Device name or ID
Make this deployment active immediately
**Returns:**
```json theme={null}
{
"id": "dep-456",
"active": false,
"jobs": []
}
```
### device\_deployment\_show
Show deployment details.
**Parameters:**
Device name or ID
Deployment ID (uses active if omitted)
**Returns:**
```json theme={null}
{
"id": "dep-123",
"active": true,
"jobs": [
{
"id": "web-app",
"enabled": true,
"type": "service"
}
]
}
```
### device\_deployment\_rm
Remove a deployment.
**Parameters:**
Device name or ID
Deployment ID to remove
**Returns:**
```json theme={null}
{"status": "removed"}
```
### device\_deployment\_active
Get the currently active deployment.
**Parameters:**
Device name or ID
**Returns:**
```json theme={null}
{
"active_deployment_id": "dep-123"
}
```
### device\_deployment\_activate
Set the active deployment.
**Parameters:**
Device name or ID
Deployment ID to activate
**Returns:**
```json theme={null}
{"status": "activated"}
```
### device\_deployment\_status **\[Batch]**
Get deployment status. Supports batch operations.
**Parameters:**
Single device name or ID (mutually exclusive with `devices`)
Multiple device names/IDs (mutually exclusive with `device`)
Deployment ID (uses active if omitted)
**Returns (single):**
```json theme={null}
{
"deployment_id": "dep-123",
"status": "running",
"jobs": [
{
"id": "web-app",
"status": "running",
"health": "healthy"
}
]
}
```
**Returns (batch):**
```json theme={null}
{
"results": [
{"device": "device-1", "deployment_id": "dep-123", ...},
{"device": "device-2", "error": "No active deployment"}
]
}
```
### device\_deployment\_clone
Clone a deployment.
**Parameters:**
Device name or ID
Source deployment ID to clone
Make cloned deployment active immediately
**Returns:**
```json theme={null}
{
"id": "dep-789",
"active": false,
"jobs": [...]
}
```
## Organization Management
### org\_list
List organizations.
**Parameters:** None
**Returns:**
```json theme={null}
[
{
"id": "my-org",
"owner": "owner@example.com",
"members": 5
}
]
```
### org\_create
Create an organization.
**Parameters:**
Organization ID
Owner email address
**Returns:**
```json theme={null}
{"status": "created"}
```
### org\_delete
Delete an organization.
**Parameters:**
Organization ID
**Returns:**
```json theme={null}
{"status": "deleted"}
```
### org\_update
Update organization.
**Parameters:**
Current organization ID
New organization ID
**Returns:**
```json theme={null}
{"status": "updated"}
```
### org\_members\_list
List organization members.
**Parameters:**
Organization ID
**Returns:**
```json theme={null}
[
{
"email": "user@example.com",
"role": "editor"
}
]
```
### org\_members\_add
Add organization member.
**Parameters:**
Organization ID
Member email address
Role: `admin`, `editor`, or `viewer`
**Returns:**
```json theme={null}
{"status": "added"}
```
### org\_members\_remove
Remove organization member.
**Parameters:**
Organization ID
Member email address
**Returns:**
```json theme={null}
{"status": "removed"}
```
### org\_devices\_list
List organization devices.
**Parameters:**
Organization ID
**Returns:**
```json theme={null}
[
{
"id": "dev-123",
"name": "my-device",
"status": "online"
}
]
```
### org\_devices\_add
Add device to organization.
**Parameters:**
Organization ID
Device name or ID
**Returns:**
```json theme={null}
{"status": "added"}
```
### org\_devices\_remove
Remove device from organization.
**Parameters:**
Organization ID
Device name or ID
**Returns:**
```json theme={null}
{"status": "removed"}
```
# Deployment Commands
Source: https://docs.make87.com/commands/deployments
Deploy and manage applications on remote devices using deploy, undeploy, and deployment commands
## Overview
m87 provides powerful deployment management for running applications on remote devices. Deployments allow you to register jobs that execute automatically when devices come online, making them ideal for managing fleets of intermittently-connected edge devices.
**Use deployments when:**
* Your devices are not always online
* You need to deploy to multiple devices
* You want automated deployment workflows
* You need to observe and monitor running services
* You want deployment rollback capabilities
***
## Deploy Command
Add a run spec (docker-compose or custom) to a deployment.
### Syntax
```bash theme={null}
m87 deploy [OPTIONS]
```
### Options
| Flag | Description |
| ---------------------- | ------------------------------------------------------------------------ |
| `--type ` | Spec type: `auto`, `compose`, `runspec`, or `deployment` (default: auto) |
| `--name ` | Optional display name for the run spec |
| `--deployment-id ` | Add to specific deployment (defaults to active deployment) |
### Examples
```bash Deploy Docker Compose theme={null}
# Auto-detect and deploy docker-compose.yml
m87 rpi deploy ./docker-compose.yml
# Explicitly specify compose type
m87 rpi deploy ./docker-compose.yml --type compose
# Deploy with custom name
m87 rpi deploy ./docker-compose.yml --name my-web-app
```
```bash Deploy Custom Run Spec theme={null}
# Deploy custom run spec YAML
m87 rpi deploy ./custom-spec.yml --type runspec
# Deploy to specific deployment
m87 rpi deploy ./spec.yml --deployment-id dep_123abc
```
```bash Deploy Full Deployment theme={null}
# Deploy complete deployment configuration
m87 rpi deploy ./deployment.yml --type deployment
```
### How Deploy Works
1. **Automatic conversion**: Docker Compose files are automatically converted to m87 run specs
2. **Active deployment**: If no deployment ID is specified, the file is added to the active deployment
3. **Auto-creation**: If no active deployment exists, a new one is created automatically
4. **Execution**: The deployment runs when the device is online
The CLI automatically detects whether your file is a Docker Compose file or a custom run spec by looking for the `services` key in the YAML.
***
## Undeploy Command
Remove a run spec from a deployment.
### Syntax
```bash theme={null}
m87 undeploy [OPTIONS]
```
### Options
| Flag | Description |
| ---------------------- | ---------------------------------------------------- |
| `--deployment-id ` | Remove from specific deployment (defaults to active) |
### Examples
```bash Remove by Name theme={null}
# Remove run spec by name
m87 rpi undeploy my-compose
# Remove from specific deployment
m87 rpi undeploy my-compose --deployment-id dep_123abc
```
```bash Remove by File Path theme={null}
# Remove using original file path
m87 rpi undeploy ./docker-compose.yml
```
Undeploy removes the run spec from the deployment configuration but doesn't immediately stop running containers. The deployment system handles cleanup according to the spec's configuration.
***
## Deployment Subcommands
Manage deployments on devices with the `deployment` subcommand group.
### List Deployments
View all deployments for a device.
```bash theme={null}
m87 deployment list
```
**Output includes:**
* Deployment ID
* Creation date
* Active status
* Number of run specs
### Create New Deployment
Create a new deployment.
```bash theme={null}
# Create inactive deployment
m87 deployment new
# Create and activate immediately
m87 deployment new --active
```
Creating multiple deployments allows you to prepare different configurations and switch between them easily.
### Show Deployment Details
View details of a deployment including all run specs.
```bash Show Active Deployment theme={null}
# Show currently active deployment
m87 deployment show
# Show as YAML
m87 deployment show --yaml
```
```bash Show Specific Deployment theme={null}
# Show specific deployment by ID
m87 deployment show --deployment-id dep_123abc
# Show specific deployment as YAML
m87 deployment show --deployment-id dep_123abc --yaml
```
### Deployment Status
Check the execution status of a deployment.
```bash Basic Status theme={null}
# View status of active deployment
m87 deployment status
# View status with logs
m87 deployment status --logs
```
```bash Specific Deployment Status theme={null}
# Check specific deployment
m87 deployment status --deployment-id dep_123abc --logs
```
**Status information includes:**
* Deployment execution state
* Run spec states (pending, running, completed, failed)
* Health check results
* Timestamps
* Logs (with `--logs` flag)
### Activate Deployment
Set which deployment is active.
```bash theme={null}
# Activate specific deployment
m87 deployment activate dep_123abc
```
Only one deployment can be active at a time. Activating a new deployment deactivates the previous one.
### View Active Deployment
Check which deployment is currently active.
```bash theme={null}
m87 deployment active
```
### Remove Deployment
Delete a deployment.
```bash Remove with Confirmation theme={null}
# Remove deployment (prompts for confirmation)
m87 deployment rm dep_123abc
```
```bash Force Remove theme={null}
# Remove without confirmation
m87 deployment rm dep_123abc --force
```
Removing a deployment cannot be undone. Make sure you have backups if needed.
### Clone Deployment
Duplicate an existing deployment.
```bash theme={null}
# Clone deployment (inactive)
m87 deployment clone dep_123abc
# Clone and activate immediately
m87 deployment clone dep_123abc --active
```
**Use cases for cloning:**
* Create staging/production variants
* Test configuration changes safely
* Prepare deployment rollbacks
### Update Deployment
Modify an existing deployment (advanced).
```bash theme={null}
m87 deployment update [OPTIONS]
```
The update command supports advanced operations like removing, replacing, moving, and renaming run specs. See `m87 deployment update --help` for detailed options.
***
## Real-World Workflows
### Initial Deployment
```bash Simple Deployment theme={null}
# 1. Deploy docker-compose file
m87 rpi deploy ./docker-compose.yml
# 2. Check status
m87 rpi deployment status --logs
# 3. Monitor execution
m87 rpi logs -f
```
```bash Multi-Service Deployment theme={null}
# 1. Deploy web service
m87 rpi deploy ./web/docker-compose.yml --name web
# 2. Deploy API service
m87 rpi deploy ./api/docker-compose.yml --name api
# 3. Deploy database
m87 rpi deploy ./db/docker-compose.yml --name database
# 4. View complete deployment
m87 rpi deployment show --yaml
```
### Update Deployment
```bash theme={null}
# 1. Make changes to docker-compose.yml locally
vim docker-compose.yml
# 2. Remove old version
m87 rpi undeploy my-app
# 3. Deploy new version
m87 rpi deploy ./docker-compose.yml --name my-app
# 4. Monitor deployment
m87 rpi deployment status --logs
```
### Staging and Production
```bash Create Staging Environment theme={null}
# 1. Create staging deployment
m87 rpi deployment new
# 2. Deploy to staging
m87 rpi deploy ./docker-compose.staging.yml \
--deployment-id \
--name staging-app
# 3. Activate staging
m87 rpi deployment activate
# 4. Test staging
m87 rpi deployment status --logs
```
```bash Promote to Production theme={null}
# 1. Clone staging to production
m87 rpi deployment clone
# 2. Update production config if needed
m87 rpi undeploy staging-app --deployment-id
m87 rpi deploy ./docker-compose.prod.yml \
--deployment-id \
--name prod-app
# 3. Activate production
m87 rpi deployment activate
```
### Rollback Deployment
```bash theme={null}
# 1. List deployments to find previous version
m87 rpi deployment list
# 2. Activate previous deployment
m87 rpi deployment activate
# 3. Verify rollback
m87 rpi deployment status --logs
```
### Fleet Deployment
```bash theme={null}
# Deploy same configuration to multiple devices
for device in rpi1 rpi2 rpi3 edge1 edge2; do
echo "Deploying to $device..."
m87 $device deploy ./docker-compose.yml --name fleet-app
done
# Check status across fleet
for device in rpi1 rpi2 rpi3 edge1 edge2; do
echo "\n=== $device ==="
m87 $device deployment status
done
```
***
## Docker Compose Conversion
When you deploy a Docker Compose file, m87 automatically converts it to a run spec.
### Example Conversion
**Input (docker-compose.yml):**
```yaml theme={null}
services:
web:
image: nginx:alpine
container_name: web
restart: unless-stopped
ports:
- "80:80"
app:
build: .
container_name: app
restart: unless-stopped
environment:
- NODE_ENV=production
```
**Deployed to device:**
```bash theme={null}
m87 rpi deploy ./docker-compose.yml --name my-web-app
```
The CLI handles:
* Converting compose format to m87 run spec
* Uploading build contexts if needed
* Configuring restart policies
* Setting up environment variables
* Managing container lifecycles
***
## Deployment vs Direct Docker Commands
| Feature | Deployment | Direct Docker |
| ------------------------ | ---------------------- | ------------------------ |
| **Works when offline** | ✓ Queued for execution | ✗ Requires online device |
| **Automatic retry** | ✓ Built-in | ✗ Manual |
| **Health monitoring** | ✓ Continuous | ✗ Manual |
| **Rollback support** | ✓ Easy | ✗ Manual |
| **Fleet management** | ✓ Centralized | ✗ Per-device |
| **Immediate execution** | Depends on device | ✓ Immediate |
| **Simple one-off tasks** | ✗ Overhead | ✓ Simple |
**Choose deployments for:**
* Production applications
* Fleet management
* Intermittent connectivity
* Automated workflows
**Choose direct docker commands for:**
* Development and debugging
* One-off tasks
* Immediate execution needs
* Interactive work
***
## Monitoring Deployments
```bash Check Deployment Status theme={null}
# View current status
m87 rpi deployment status
# View with execution logs
m87 rpi deployment status --logs
```
```bash View Container Logs theme={null}
# View logs from deployed containers
m87 rpi logs -f
# View logs with specific tail
m87 rpi logs --tail 100
```
```bash Check Device Status theme={null}
# Overall device health
m87 rpi status
# View system metrics
m87 rpi metrics
```
***
## Best Practices
**Deployment best practices:**
1. **Use version tags** - Tag deployments with version numbers for easy tracking
2. **Test in staging** - Create staging deployments before production
3. **Keep backups** - Clone deployments before making major changes
4. **Monitor logs** - Use `--logs` flag to catch issues early
5. **Use meaningful names** - Name run specs clearly with `--name` flag
6. **Document configs** - Add comments to docker-compose files
7. **Regular cleanup** - Remove old deployments you no longer need
***
## Related Commands
* [Docker Integration](/commands/docker-integration) - Direct Docker commands
* [Device Access](/commands/device-access) - Monitor deployed applications
* [File Transfer](/commands/file-transfer) - Deploy application files
# Device Access Commands
Source: https://docs.make87.com/commands/device-access
Access and interact with remote devices using shell, exec, status, and audit commands
## Overview
The m87 CLI provides several commands for accessing and monitoring remote devices. These commands allow you to open interactive shells, execute commands remotely, check device status, and audit device access logs.
## Shell Command
Open a persistent interactive shell session on a remote device.
### Syntax
```bash theme={null}
m87 shell
```
### Description
The `shell` command provides a full interactive shell with PTY support, similar to SSH. It opens a persistent bash session on the remote device with full terminal capabilities.
### Features
* **Full PTY support** - Colors, cursor control, and terminal formatting
* **TUI applications** - Works with vim, htop, less, nano, and other terminal UI apps
* **Default shell** - Uses the user's default shell defined in `$SHELL`
* **Easy exit** - Press `Ctrl+D` to exit the shell session
### Examples
```bash Connect to Raspberry Pi theme={null}
m87 rpi shell
# You're now in a remote shell
pi@raspberrypi:~ $ ls -la
pi@raspberrypi:~ $ htop
pi@raspberrypi:~ $ vim config.yaml
pi@raspberrypi:~ $ exit
```
```bash Interactive Administration theme={null}
m87 edge-device shell
# Explore files, install packages, configure services interactively
```
For scripting and automation, use `exec` instead of `shell`. The `shell` command is designed for interactive work only.
### Shell vs Exec Comparison
| Feature | `shell` | `exec` |
| ------------------ | -------- | ---------------- |
| Persistent session | ✓ Yes | ✗ No |
| Multiple commands | ✓ Yes | Single command |
| Interactive apps | ✓ Always | With `-it` flags |
| Scripting | ✗ No | ✓ Yes |
***
## Exec Command
Execute commands on remote devices with optional stdin forwarding and TTY support.
### Syntax
```bash theme={null}
m87 exec [OPTIONS] --
```
### Options
| Flag | Description |
| ------------- | ------------------------------------------ |
| `-i, --stdin` | Keep stdin open for responding to prompts |
| `-t, --tty` | Allocate a pseudo-TTY for TUI applications |
### Execution Modes
| Flags | Mode | Use Case |
| ------ | ---------------- | --------------------------------- |
| (none) | Output only | Simple commands, scripts |
| `-i` | Stdin forwarding | Simple prompts (Y/n), piped input |
| `-t` | TTY read-only | Colored output, watch mode |
| `-it` | Full TTY | sudo, TUI apps (vim, htop, less) |
### Examples
```bash System Administration theme={null}
# Check disk usage
m87 rpi exec -- df -h
# Update packages (needs TTY for sudo password)
m87 rpi exec -it -- 'sudo apt update && sudo apt upgrade'
# View system logs
m87 rpi exec -- journalctl -n 100
```
```bash Docker Management theme={null}
# List containers
m87 rpi exec -- docker ps -a
# View container logs
m87 rpi exec -- docker logs myapp
# Stop all containers
m87 rpi exec -- 'docker stop $(docker ps -q)'
```
```bash Interactive Applications theme={null}
# Edit a file with vim
m87 rpi exec -it -- vim /etc/hosts
# Monitor with htop
m87 rpi exec -it -- htop
# Browse files with less
m87 rpi exec -it -- less /var/log/syslog
```
```bash Chained Commands theme={null}
# Multiple commands with &&
m87 rpi exec -- 'cd /app && git pull && npm install'
# Pipeline
m87 rpi exec -- 'ps aux | grep nginx'
```
### Shell Quoting
Commands are interpreted by your local shell first. Use single quotes to send commands literally to the remote device.
```bash theme={null}
# ❌ Local shell expands $(...)
m87 rpi exec -- docker kill $(docker ps -q) # Runs docker ps -q locally!
# ✓ Single quotes send literally to remote
m87 rpi exec -- 'docker kill $(docker ps -q)' # Expands on remote
```
### Ctrl+C Behavior
| Mode | Ctrl+C Effect |
| --------------- | ------------------------------------------ |
| No flags / `-i` | Terminates connection, exits with code 130 |
| `-t` | No effect (stdin not connected) |
| `-it` | Sent to remote app (e.g., cancel in vim) |
**TTY flag behavior:** The `-t` flag without `-i` allocates a TTY for output formatting but does not connect stdin. This means keyboard input (including Ctrl+C) has no effect. Use `-t` alone for commands that need colored/formatted output but no interaction.
### Process Cleanup
When the connection closes (Ctrl+C, network drop, etc.), the remote process is automatically terminated. No orphaned processes are left on the device.
***
## Status Command
Check the status of a remote device including health, crashes, and incidents.
### Syntax
```bash theme={null}
m87 status
```
### Description
Displays comprehensive status information about the device, including:
* Connection status (online/offline)
* System health metrics
* Recent crashes or errors
* Active incidents
* Runtime information
### Example
```bash theme={null}
m87 rpi-garage status
```
Use `m87 devices list` to see the status of all devices at once.
***
## Audit Command
View audit logs showing who interacted with the device and when.
### Syntax
```bash theme={null}
m87 audit [OPTIONS]
```
### Options
| Flag | Default | Description |
| ---------------- | ------- | ----------------------------------------------------------------------------- |
| `--since ` | - | Filter logs from this RFC 3339 date (e.g., 2026-01-31 or 2026-01-31T13:00:00) |
| `--until ` | - | Filter logs up to this RFC 3339 date |
| `--max ` | 100 | Maximum number of logs to return |
| `--details` | false | Show detailed information for each audit entry |
### Examples
```bash Basic Audit Log theme={null}
# View recent audit logs
m87 rpi audit
```
```bash Detailed Audit Log theme={null}
# View detailed audit information
m87 rpi audit --details
```
```bash Time-based Filtering theme={null}
# View audit logs from the last week
m87 rpi audit --since 2026-02-24
# View logs for a specific date range
m87 rpi audit --since 2026-02-01 --until 2026-02-28
```
```bash Limit Results theme={null}
# Get last 50 audit entries with details
m87 rpi audit --max 50 --details
```
### Audit Log Information
Audit logs capture:
* User access events (shell, exec, etc.)
* Command executions
* File transfers
* Port forwarding sessions
* Deployment operations
* Timestamps and user identifiers
Audit logs help with compliance, security monitoring, and troubleshooting device access issues.
***
## Related Commands
* [File Transfer](/commands/file-transfer) - Copy and sync files with devices
* [Port Forwarding](/commands/port-forwarding) - Forward ports from remote devices
* [Docker Integration](/commands/docker-integration) - Manage Docker containers remotely
# Docker Integration Commands
Source: https://docs.make87.com/commands/docker-integration
Manage Docker containers and run docker-compose on remote devices
## Overview
The m87 CLI provides seamless Docker integration, allowing you to run any Docker command on remote devices as if you were working locally. This includes full support for Docker Compose for deploying multi-container applications.
***
## Docker Command
Execute Docker commands on remote devices using Docker passthrough.
### Syntax
```bash theme={null}
m87 docker ...
```
All standard Docker commands and flags are supported.
### Prerequisites
**Before using Docker commands:**
1. Docker must be installed on the remote device
2. The device user must be in the `docker` group or have root permissions
3. Docker daemon must be running
To add user to docker group:
```bash theme={null}
m87 exec -it -- sudo usermod -aG docker $USER
```
***
## Container Management
### List and Inspect Containers
```bash List Containers theme={null}
# List running containers
m87 rpi docker ps
# List all containers (including stopped)
m87 rpi docker ps -a
# List with custom format
m87 rpi docker ps --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
```
```bash Inspect Containers theme={null}
# View container details
m87 rpi docker inspect myapp
# View container logs
m87 rpi docker logs myapp
# Follow logs in real-time
m87 rpi docker logs -f myapp
# View last 100 lines
m87 rpi docker logs --tail 100 myapp
```
### Container Lifecycle
```bash Run Containers theme={null}
# Run container in detached mode
m87 rpi docker run -d --name nginx -p 80:80 nginx
# Run with environment variables
m87 rpi docker run -d --name app \
-e NODE_ENV=production \
-e PORT=3000 \
-p 3000:3000 \
myapp:latest
# Run with volume mount
m87 rpi docker run -d --name db \
-v postgres-data:/var/lib/postgresql/data \
postgres:15
```
```bash Control Containers theme={null}
# Stop container
m87 rpi docker stop myapp
# Start stopped container
m87 rpi docker start myapp
# Restart container
m87 rpi docker restart myapp
# Remove container
m87 rpi docker rm myapp
# Force remove running container
m87 rpi docker rm -f myapp
```
### Bulk Operations
```bash theme={null}
# Stop all running containers
m87 rpi docker ps -q | xargs -r m87 rpi docker stop
# Remove all stopped containers
m87 rpi docker container prune -f
# Kill all running containers
m87 rpi docker ps -q | xargs -r m87 rpi docker kill
```
Be careful with bulk operations. Always verify which containers will be affected by running `m87 docker ps` first.
***
## Image Management
```bash List and Pull Images theme={null}
# List images
m87 rpi docker images
# Pull image from Docker Hub
m87 rpi docker pull nginx:alpine
# Pull specific version
m87 rpi docker pull postgres:15.3
# Pull from private registry
m87 rpi docker pull myregistry.com/myapp:latest
```
```bash Build Images theme={null}
# Build image from Dockerfile in current directory
m87 rpi docker build -t myapp:latest .
# Build with custom Dockerfile
m87 rpi docker build -f Dockerfile.prod -t myapp:prod .
# Build with build args
m87 rpi docker build \
--build-arg NODE_VERSION=18 \
--build-arg ENV=production \
-t myapp:latest .
```
```bash Manage Images theme={null}
# Tag image
m87 rpi docker tag myapp:latest myapp:v1.0.0
# Remove image
m87 rpi docker rmi myapp:old
# Remove unused images
m87 rpi docker image prune -a -f
```
***
## Docker Compose
Deploy and manage multi-container applications with Docker Compose.
### Basic Compose Commands
```bash Deploy Stack theme={null}
# Deploy from local directory with docker-compose.yml
cd my-project
m87 rpi docker compose up -d
# Specify compose file location
m87 rpi docker compose --project-directory ./my-app up -d
# Deploy specific services
m87 rpi docker compose up -d web api
```
```bash Manage Stack theme={null}
# View stack status
m87 rpi docker compose ps
# View logs
m87 rpi docker compose logs
# Follow logs
m87 rpi docker compose logs -f
# View logs for specific service
m87 rpi docker compose logs -f web
```
```bash Update and Rebuild theme={null}
# Rebuild and restart services
m87 rpi docker compose up -d --build
# Pull latest images and restart
m87 rpi docker compose pull
m87 rpi docker compose up -d
# Restart specific service
m87 rpi docker compose restart web
```
```bash Stop and Remove theme={null}
# Stop services (keeps containers)
m87 rpi docker compose stop
# Stop and remove containers
m87 rpi docker compose down
# Remove containers and volumes
m87 rpi docker compose down -v
# Remove containers, volumes, and images
m87 rpi docker compose down -v --rmi all
```
***
## Example: Simple Compose Project
Here's a working example that demonstrates Docker Compose with m87:
### docker-compose.yml
```yaml theme={null}
services:
pause:
image: registry.k8s.io/pause:3.9
container_name: test-pause
restart: unless-stopped
custom:
build:
context: .
dockerfile: Dockerfile
container_name: test-custom
init: true
restart: unless-stopped
environment:
- TEST_VAR=hello-from-compose
```
### Deploy Example
```bash Deploy theme={null}
cd examples/features/docker-compose/simple
m87 rpi docker compose up -d
```
```bash Monitor theme={null}
# View running containers
m87 rpi docker compose ps
# View logs
m87 rpi docker compose logs -f
```
```bash Teardown theme={null}
# Stop and remove
m87 rpi docker compose down
```
***
## Advanced Docker Commands
### Network Management
```bash theme={null}
# List networks
m87 rpi docker network ls
# Create network
m87 rpi docker network create mynetwork
# Connect container to network
m87 rpi docker network connect mynetwork myapp
# Inspect network
m87 rpi docker network inspect mynetwork
```
### Volume Management
```bash theme={null}
# List volumes
m87 rpi docker volume ls
# Create volume
m87 rpi docker volume create mydata
# Inspect volume
m87 rpi docker volume inspect mydata
# Remove unused volumes
m87 rpi docker volume prune -f
```
### System Information
```bash theme={null}
# Check Docker version
m87 rpi docker version
# View system info
m87 rpi docker info
# View disk usage
m87 rpi docker system df
# Clean up everything
m87 rpi docker system prune -a --volumes -f
```
***
## Real-World Workflows
### Deploy Web Application
```bash Initial Deployment theme={null}
# 1. Sync application code
m87 sync ./myapp rpi:myapp --exclude node_modules
# 2. Build and run containers
m87 rpi docker compose --project-directory ~/myapp up -d --build
# 3. Check status
m87 rpi docker compose --project-directory ~/myapp ps
```
```bash Update Application theme={null}
# 1. Sync code changes
m87 sync ./myapp rpi:myapp --exclude node_modules
# 2. Rebuild and restart
m87 rpi docker compose --project-directory ~/myapp up -d --build
# 3. View logs
m87 rpi docker compose --project-directory ~/myapp logs -f
```
### Database Backup
```bash theme={null}
# Create database backup
m87 rpi docker exec postgres pg_dump -U postgres mydb > backup.sql
# Download backup
m87 cp rpi:backup.sql ./backups/mydb-$(date +%Y%m%d).sql
```
### Development with Hot Reload
```bash theme={null}
# Terminal 1: Watch and sync code
m87 sync ./src rpi:project/src --watch --exclude node_modules
# Terminal 2: Watch application logs
m87 rpi docker compose --project-directory ~/project logs -f web
```
### Container Debugging
```bash theme={null}
# Execute shell in running container
m87 rpi docker exec -it myapp /bin/bash
# View container processes
m87 rpi docker top myapp
# View container resource usage
m87 rpi docker stats myapp
# View container filesystem changes
m87 rpi docker diff myapp
```
***
## Docker with Port Forwarding
Access containerized services locally:
```bash theme={null}
# 1. Deploy application
m87 rpi docker run -d --name web -p 8080:80 nginx
# 2. Forward port to local machine
m87 rpi forward 8080
# 3. Access at http://localhost:8080
```
***
## Troubleshooting
### Check Docker Status
```bash theme={null}
# Verify Docker is running
m87 rpi exec -- systemctl status docker
# Check Docker daemon logs
m87 rpi exec -- journalctl -u docker -n 50
```
### Permission Issues
```bash theme={null}
# Add user to docker group
m87 rpi exec -it -- sudo usermod -aG docker $USER
# Verify group membership (requires re-login)
m87 rpi exec -- groups
```
### Container Won't Start
```bash theme={null}
# Check container logs
m87 rpi docker logs myapp
# Inspect container configuration
m87 rpi docker inspect myapp
# Check resource usage
m87 rpi docker stats --no-stream
```
***
## Performance Tips
**Optimize Docker operations:**
* Use `.dockerignore` to exclude unnecessary files from build context
* Use multi-stage builds to reduce image size
* Clean up unused images and containers regularly with `docker system prune`
* Use specific image tags instead of `latest` for reproducibility
* Mount volumes for persistent data instead of storing in containers
***
## Related Commands
* [Deployments](/commands/deployments) - Automated deployment management
* [Device Access](/commands/device-access) - Execute commands on devices
* [Port Forwarding](/commands/port-forwarding) - Access container ports locally
* [File Transfer](/commands/file-transfer) - Sync application code to devices
# File Transfer Commands
Source: https://docs.make87.com/commands/file-transfer
Copy and synchronize files between local and remote devices using cp and sync commands
## Overview
m87 provides powerful file transfer capabilities using `device:path` syntax to reference remote locations. All file transfers use SFTP over the secure m87 tunnel.
## Path Syntax
Remote paths use scp-style resolution:
```text theme={null}
: Remote path on device
Local path
```
### Path Resolution Rules
* **Relative paths** (no leading `/`) → Resolve to user's home directory
* **Absolute paths** (starting with `/`) → Used as-is
* **Tilde expansion** (`~`) → Expands to home directory
### Examples
```bash theme={null}
rpi:app # Remote ~/app directory on device "rpi"
rpi:/etc/config # Absolute path /etc/config
rpi:~/logs # Explicit home directory path
./src # Local directory
```
***
## Copy Command (cp)
Copy individual files between local and remote devices (SCP-style).
### Syntax
```bash theme={null}
m87 cp
```
Either source or destination must be a remote path using `:` format.
### Examples
```bash Upload to Device theme={null}
# Copy local file to remote (relative path → ~/config.json)
m87 cp ./config.json rpi:config.json
# Copy local file to absolute path
m87 cp ./config.json rpi:/etc/myapp/config.json
# Copy entire directory
m87 cp ./dist rpi:www
```
```bash Download from Device theme={null}
# Copy remote file to local
m87 cp rpi:logs/app.log ./app.log
# Download from absolute path
m87 cp rpi:/var/log/syslog ./syslog.txt
```
```bash Device-to-Device Transfer theme={null}
# Copy between remote devices
m87 cp rpi:data.db jetson:backup/data.db
```
The `cp` command is best for copying individual files or directories. For continuous synchronization with change detection, use the `sync` command.
***
## Sync Command
Synchronize directories between local and remote devices (rsync-style).
### Syntax
```bash theme={null}
m87 sync [OPTIONS]
```
### Options
| Flag | Short | Description |
| --------------------- | ----- | ----------------------------------------------------------- |
| `--delete` | - | Remove files from destination not present in source |
| `--watch` | - | Continuously sync on file changes (polls every 2s) |
| `--dry-run` | `-n` | Show what would be synced without making changes |
| `--exclude ` | `-e` | Exclude files matching pattern (can be used multiple times) |
### Basic Examples
```bash Push to Device theme={null}
# Push local directory to remote home
m87 sync ./src rpi:app
# Push to absolute path
m87 sync ./src rpi:/home/pi/app
```
```bash Pull from Device theme={null}
# Pull remote directory to local
m87 sync rpi:/var/log ./logs
# Pull application data
m87 sync rpi:myapp/data ./backup/data
```
```bash Delete Removed Files theme={null}
# Sync and delete files not in source
m87 sync ./deploy rpi:app --delete
```
```bash Watch Mode theme={null}
# Watch for changes and auto-sync
m87 sync ./src rpi:app --watch
# Watch with delete
m87 sync ./src rpi:app --watch --delete
```
### Exclude Patterns
The `--exclude` flag supports:
* **Exact names**: `--exclude node_modules` (matches any path component)
* **Wildcards**: `--exclude "*.log"` (matches `app.log`, `error.log`, etc.)
```bash Common Excludes theme={null}
# Exclude build artifacts and dependencies
m87 sync ./project rpi:project \
--exclude node_modules \
--exclude .git \
--exclude __pycache__ \
--exclude "*.pyc" \
--exclude ".env"
```
```bash Development Workflow theme={null}
# Watch and sync, excluding build outputs
m87 sync ./src rpi:project --watch \
--exclude node_modules \
--exclude dist \
--exclude "*.log"
```
### Dry Run
Preview what will be synced without making changes:
```bash theme={null}
# Preview sync operation
m87 sync ./dist rpi:www --delete --dry-run
# If satisfied, run the actual sync
m87 sync ./dist rpi:www --delete
```
Always use `--dry-run` first when using `--delete` to ensure you won't accidentally remove important files.
***
## List Files Command (ls)
List contents of a remote directory.
### Syntax
```bash theme={null}
m87 ls :
```
### Examples
```bash theme={null}
# List files in home directory
m87 ls rpi:projects
# List system logs
m87 ls rpi:/var/log
# List root directory
m87 ls rpi:/
```
***
## Real-World Examples
### Deploy Application
```bash Initial Deploy theme={null}
# Sync source code to device
m87 sync ./app rpi:myapp
# Connect and install dependencies
m87 rpi exec -- 'cd ~/myapp && npm install && pm2 restart all'
```
```bash Update Deploy theme={null}
# Sync changes excluding dependencies
m87 sync ./app rpi:myapp --exclude node_modules
# Restart application
m87 rpi exec -- 'pm2 restart myapp'
```
### Development Workflow
```bash theme={null}
# Terminal 1: Watch and sync code changes
m87 sync ./src rpi:project --watch \
--exclude node_modules \
--exclude dist \
--exclude "*.log"
# Terminal 2: Watch application logs
m87 rpi logs -f
```
### Backup Remote Files
```bash Backup Logs theme={null}
# Pull application logs
m87 sync rpi:/var/log/myapp ./backups/logs
# Pull with timestamp in directory name
m87 sync rpi:/var/log/myapp ./backups/logs-$(date +%Y%m%d)
```
```bash Backup Configuration theme={null}
# Pull config files
m87 sync rpi:/etc/myapp ./backups/config
# Copy single config file
m87 cp rpi:/etc/myapp/config.yaml ./config-backup.yaml
```
### Clean Deploy with Verification
```bash theme={null}
# 1. Preview what will change
m87 sync ./dist rpi:www --delete --dry-run
# 2. Review the output carefully
# 3. If satisfied, run the actual sync
m87 sync ./dist rpi:www --delete
# 4. Verify deployment
m87 rpi exec -- ls -la ~/www
```
### Quick Script Deployment
```bash theme={null}
# Upload a deployment script
m87 cp ./deploy.sh rpi:deploy.sh
# Make it executable
m87 rpi exec -- chmod +x ~/deploy.sh
# Run the script
m87 rpi exec -it -- ./deploy.sh
```
### Database Backup
```bash theme={null}
# Create backup on remote device
m87 rpi exec -- pg_dump mydb > ~/backup.sql
# Download the backup
m87 cp rpi:backup.sql ./backups/db-$(date +%Y%m%d).sql
```
***
## Performance Notes
* File transfers use SFTP over the m87 secure tunnel
* Large files are transferred efficiently without loading into memory
* `--watch` mode polls for changes every 2 seconds
* Relative remote paths resolve to home directory for convenience
***
## Related Commands
* [Device Access](/commands/device-access) - Execute commands after file transfer
* [Deployments](/commands/deployments) - Automated deployment workflows
* [Docker Integration](/commands/docker-integration) - Deploy containerized applications
# Port Forwarding Commands
Source: https://docs.make87.com/commands/port-forwarding
Forward ports and serial devices from remote machines using forward and serial commands
## Overview
m87 port forwarding allows you to securely access services running on remote devices or devices on remote networks as if they were running locally. All traffic is encrypted through the m87 secure tunnel.
***
## Forward Command
Forward ports from remote devices or remote network devices to your local machine.
### Syntax
```bash theme={null}
m87 forward ...
```
Each target follows the format: `[local_port:]remote_target[/protocol]`
Where:
* `remote_target` is `[host:]port`
* `protocol` is `tcp` (default) or `udp`
### Port Forwarding Formats
| Format | Description | Example |
| ------------------- | ------------------------------------------- | ----------------------- |
| `port` | Forward remote port to same local port | `8080` |
| `local:remote` | Forward remote port to different local port | `3000:8080` |
| `host:port` | Forward from device on remote network | `192.168.1.50:554` |
| `local:host:remote` | Map remote network device to local port | `8554:192.168.1.50:554` |
| `port/protocol` | Specify protocol (tcp or udp) | `8080/udp` |
### Basic Examples
```bash Same Port Forwarding theme={null}
# Forward remote port 8080 to local port 8080
m87 rpi forward 8080
# Access at http://localhost:8080
```
```bash Different Port Mapping theme={null}
# Forward remote port 8080 to local port 3000
m87 rpi forward 3000:8080
# Access at http://localhost:3000
```
```bash Explicit TCP theme={null}
# TCP is the default protocol
m87 rpi forward 8080/tcp
```
```bash UDP Forwarding theme={null}
# Forward UDP traffic (e.g., DNS)
m87 rpi forward 53/udp
```
***
## Network Device Forwarding
Access devices on the remote device's network, not just services running on the device itself.
### Use Cases
* Access IP cameras on remote LANs
* Connect to routers and switches through a jump host
* Reach internal network services
* Access IoT devices without public IPs
### Examples
```bash IP Camera Access theme={null}
# Forward port 554 from IP camera at 192.168.1.50
m87 rpi forward 192.168.1.50:554
# Or expose locally on different port
m87 rpi forward 8554:192.168.1.50:554
# View RTSP stream locally
ffplay rtsp://localhost:8554/stream
vlc rtsp://localhost:8554/stream
```
```bash Router Admin Panel theme={null}
# Router at 192.168.1.1 only accessible from office network
m87 office-pc forward 8080:192.168.1.1:80
# Open http://localhost:8080 in browser
```
```bash Internal Database theme={null}
# Access database on internal network
m87 gateway forward 5432:10.0.1.100:5432
# Connect to PostgreSQL
psql -h localhost -p 5432 -U myuser mydb
```
***
## Multiple Port Forwarding
Forward multiple ports with a single command.
### Examples
```bash Same Ports theme={null}
# Forward multiple ports (same local and remote)
m87 device forward 8080 9090 3000
```
```bash Mixed Port Mapping theme={null}
# Forward web UI and API with different local ports
m87 device forward 3000:3000 8080:8000
# Access frontend at http://localhost:3000
# Access API at http://localhost:8080
```
```bash Full Stack Application theme={null}
# Forward frontend, backend, and database
m87 app-server forward 3000 8000 5432
```
***
## Port Ranges
Forward ranges of ports efficiently.
### Syntax
```text theme={null}
start-end Forward port range (same local/remote)
start-end:start-end Map local range to different remote range
start-end:host:start-end Range with specific remote host
```
### Examples
```bash theme={null}
# Forward ports 8080-8090 (same local and remote)
m87 device forward 8080-8090
# Map local range to different remote range
m87 device forward 8080-8090:9080-9090
# Forward range from network device
m87 device forward 8080-8090:192.168.1.50:9080-9090/tcp
```
Port ranges are particularly useful for applications that use multiple consecutive ports, such as media servers or game servers.
***
## Real-World Examples
### Web Development
```bash Development Server theme={null}
# Remote Node.js app on port 3000
m87 dev-server forward 3000
# Access at http://localhost:3000
curl http://localhost:3000
```
```bash Web UI and API theme={null}
# Forward both frontend and backend
m87 rpi forward 8080:80 3000:3000
# Frontend at http://localhost:8080
# API at http://localhost:3000
```
### Database Access
```bash PostgreSQL theme={null}
# Forward PostgreSQL port
m87 db-server forward 5432
# Connect with psql
psql -h localhost -p 5432 -U myuser mydb
```
```bash MongoDB theme={null}
# Forward MongoDB with different local port
m87 db-server forward 27018:27017
# Connect with mongo client
mongo --host localhost --port 27018
```
```bash Redis theme={null}
# Forward Redis
m87 cache-server forward 6379
# Connect with redis-cli
redis-cli -h localhost -p 6379
```
### IoT and Smart Home
```bash Home Assistant theme={null}
# Access Home Assistant on remote Raspberry Pi
m87 rpi forward 8123
# Open http://localhost:8123 in browser
```
```bash MQTT Broker theme={null}
# Forward MQTT broker
m87 iot-gateway forward 1883
# Publish message
mosquitto_pub -h localhost -p 1883 -t test/topic -m "hello"
```
### Media Streaming
```bash theme={null}
# Forward RTSP stream from camera
m87 rpi forward 8554:192.168.1.50:554
# Stream with VLC
vlc rtsp://localhost:8554/stream
# Stream with ffmpeg
ffplay rtsp://localhost:8554/stream
```
### SSH Tunneling
```bash theme={null}
# Forward SSH from another device on remote network
m87 gateway forward 2222:10.0.1.100:22
# SSH to internal device through tunnel
ssh -p 2222 user@localhost
```
***
## Serial Command
Connect to serial devices (USB, UART) on remote machines.
### Syntax
```bash theme={null}
m87 serial [BAUD]
```
### Parameters
| Parameter | Required | Default | Description |
| --------- | -------- | ------- | -------------------------------------------- |
| `PATH` | Yes | - | Path to serial device (e.g., `/dev/ttyUSB0`) |
| `BAUD` | No | 115200 | Baud rate for serial connection |
### Examples
```bash Default Baud Rate theme={null}
# Connect to USB serial device (115200 baud)
m87 rpi serial /dev/ttyUSB0
```
```bash Custom Baud Rate theme={null}
# Connect with 9600 baud
m87 rpi serial /dev/ttyUSB0 9600
# Connect with 57600 baud
m87 rpi serial /dev/ttyACM0 57600
```
### Common Serial Devices
| Device Path | Description |
| -------------- | ----------------------------- |
| `/dev/ttyUSB0` | USB-to-serial adapter |
| `/dev/ttyACM0` | Arduino, USB CDC devices |
| `/dev/ttyAMA0` | Raspberry Pi UART (GPIO pins) |
| `/dev/ttyS0` | Built-in serial port |
### Use Cases
* **Arduino development** - Flash and debug Arduino boards
* **Router/switch configuration** - Access console ports
* **Embedded systems** - Debug embedded Linux devices
* **Industrial equipment** - Monitor and control serial devices
* **IoT devices** - Configure ESP32, ESP8266, and similar devices
### Serial Examples
```bash Arduino Debugging theme={null}
# Connect to Arduino on remote Pi
m87 rpi serial /dev/ttyACM0
# You can now use Arduino Serial Monitor output
```
```bash Network Switch Configuration theme={null}
# Access switch console through remote device
m87 admin-pc serial /dev/ttyUSB0 9600
# Configure switch via console
```
```bash ESP32 Development theme={null}
# Connect to ESP32 for debugging
m87 dev-board serial /dev/ttyUSB0 115200
# View ESP32 serial output
```
Use `m87 exec -- ls /dev/tty*` to list available serial devices on the remote machine.
***
## Security Features
**All forwarding is secure by default:**
* All traffic is encrypted through the m87 secure tunnel
* No need to expose ports publicly or configure firewalls
* Authentication handled automatically by m87
* End-to-end encryption for all forwarded connections
***
## Connection Behavior
* Forwarding runs in the foreground
* Press `Ctrl+C` to stop forwarding
* Connection automatically reconnects if network drops
* Local ports are released when forwarding stops
***
## Related Commands
* [Device Access](/commands/device-access) - Execute commands on remote devices
* [Docker Integration](/commands/docker-integration) - Forward ports from containers
# Runtime Management Commands
Source: https://docs.make87.com/commands/runtime-management
Manage the m87 runtime service on edge devices with start, stop, restart, enable, disable, and status commands
## Overview
The m87 runtime is a service that runs on edge devices to enable remote access from the m87 CLI. Runtime management commands allow you to install, configure, and control this service using systemd.
**Runtime commands are only available on Linux systems.** These commands manage the systemd service on edge devices, not on your local workstation.
***
## Prerequisites
* **Linux system** (amd64 or arm64)
* **systemd** installed and running
* **sudo access** (commands automatically use sudo when needed)
***
## Runtime Commands Overview
| Command | Description | Enables on Boot | Starts Service |
| --------------- | ------------------------- | --------------- | -------------- |
| `start` | Start service now | ✓ | ✓ |
| `stop` | Stop service now | No change | ✗ |
| `restart` | Restart service | No change | ✓ |
| `enable` | Enable auto-start on boot | ✓ | ✗ |
| `enable --now` | Enable and start | ✓ | ✓ |
| `disable` | Disable auto-start | ✗ | No change |
| `disable --now` | Disable and stop | ✗ | ✗ |
| `status` | Show service status | - | - |
***
## Runtime Login
Register the device as a runtime (headless flow, requires approval).
### Syntax
```bash theme={null}
m87 runtime login [OPTIONS]
```
### Options
| Flag | Description |
| ----------------- | --------------------------------------- |
| `--org-id ` | Register under specific organization ID |
| `--email ` | Register under user's email address |
You must specify either `--org-id` or `--email`, but not both.
### Examples
```bash Register with Organization theme={null}
# Register device under organization
m87 runtime login --org-id my-company
```
```bash Register with Email theme={null}
# Register device under user account
m87 runtime login --email user@example.com
```
### Approval Process
1. Run `m87 runtime login` on the edge device
2. The device shows up as "pending" in your device list
3. From your workstation, approve the device:
```bash theme={null}
m87 devices list
m87 devices approve
```
4. The device is now accessible remotely
***
## Runtime Logout
Remove runtime credentials from the device.
```bash theme={null}
m87 runtime logout
```
This deregisters the device and removes local authentication tokens.
***
## Start Runtime Service
Start the runtime service immediately and enable auto-start on boot.
### Syntax
```bash theme={null}
m87 runtime start [OPTIONS]
```
### Options
| Flag | Description |
| ----------------- | -------------------------------- |
| `--org-id ` | Organization ID for registration |
| `--email ` | Email address for registration |
### Behavior
1. Installs systemd service file
2. Enables service to start on boot
3. Starts the service immediately
4. Registers device if not already registered
### Examples
```bash Basic Start theme={null}
# Start runtime service
m87 runtime start
```
```bash Start with Registration theme={null}
# Start and register under organization
m87 runtime start --org-id my-company
# Start and register under email
m87 runtime start --email user@example.com
```
Use `m87 runtime start` as the quickest way to set up a new device. It handles installation, registration, and startup in one command.
***
## Stop Runtime Service
Stop the running service (keeps enabled at boot).
### Syntax
```bash theme={null}
m87 runtime stop
```
### Behavior
* Stops the currently running service
* Service remains enabled to start on next boot
* Device becomes unreachable until service is started again
### Example
```bash theme={null}
# Stop runtime service
m87 runtime stop
```
Stopping the runtime makes the device inaccessible remotely. You'll need physical or local network access to start it again.
***
## Restart Runtime Service
Restart the service (starts if stopped).
### Syntax
```bash theme={null}
m87 runtime restart [OPTIONS]
```
### Options
| Flag | Description |
| ----------------- | -------------------------------- |
| `--org-id ` | Organization ID for registration |
| `--email ` | Email address for registration |
### Behavior
* If running: restarts the service
* If stopped: starts the service
* Matches standard systemd `restart` behavior
### Examples
```bash Basic Restart theme={null}
# Restart runtime service
m87 runtime restart
```
```bash Common Use Case: After Update theme={null}
# Update CLI and restart runtime
m87 update
m87 runtime restart
```
### Remote Update and Restart
Update a remote device's runtime:
```bash theme={null}
# Update and restart remote device
m87 rpi exec -it -- 'm87 update && m87 runtime restart'
```
***
## Enable Runtime Service
Configure service to auto-start on boot.
### Syntax
```bash theme={null}
m87 runtime enable [OPTIONS]
```
### Options
| Flag | Description |
| ----------------- | ------------------------------------ |
| `--now` | Enable AND start service immediately |
| `--org-id ` | Organization ID for registration |
| `--email ` | Email address for registration |
### Behavior
* **Without `--now`**: Enables service for next boot (doesn't start now)
* **With `--now`**: Enables service AND starts it immediately
### Examples
```bash Enable for Next Boot theme={null}
# Enable auto-start (doesn't start now)
m87 runtime enable
# Device will start runtime on next reboot
```
```bash Enable and Start Now theme={null}
# Enable and start immediately
m87 runtime enable --now
# Equivalent to 'm87 runtime start'
```
***
## Disable Runtime Service
Remove auto-start on boot.
### Syntax
```bash theme={null}
m87 runtime disable [OPTIONS]
```
### Options
| Flag | Description |
| ------- | ------------------------------------ |
| `--now` | Disable AND stop service immediately |
### Behavior
* **Without `--now`**: Disables auto-start (keeps running now)
* **With `--now`**: Disables auto-start AND stops service immediately
### Examples
```bash Disable Auto-Start theme={null}
# Disable auto-start (keeps running now)
m87 runtime disable
# Service runs until stopped or rebooted
```
```bash Disable and Stop theme={null}
# Disable and stop immediately
m87 runtime disable --now
# Service is stopped and won't start on boot
```
***
## Runtime Status
Show local runtime service status.
### Syntax
```bash theme={null}
m87 runtime status
```
### Example Output
```bash theme={null}
m87 runtime status
```
```text theme={null}
● m87-runtime.service - m87 Runtime Service
Loaded: loaded (/etc/systemd/system/m87-runtime.service; enabled)
Active: active (running) since Mon 2026-03-01 10:23:45 UTC; 2 days ago
Main PID: 1234 (m87)
Tasks: 8 (limit: 4915)
Memory: 24.5M
CPU: 1min 32.456s
CGroup: /system.slice/m87-runtime.service
└─1234 /usr/local/bin/m87 runtime run
```
***
## Runtime Run (Advanced)
Run the runtime daemon directly (used by systemd service).
### Syntax
```bash theme={null}
m87 runtime run [OPTIONS]
```
### Options
| Flag | Description |
| ----------------- | -------------------------------- |
| `--org-id ` | Organization ID for registration |
| `--email ` | Email address for registration |
### Behavior
* Runs in foreground (blocking)
* Registers device if needed
* Waits for approval if pending
* Handles incoming connections
This command is typically only used by the systemd service. For normal operation, use `m87 runtime start` instead.
### Example
```bash theme={null}
# Run runtime in foreground (for testing)
m87 runtime run --email user@example.com
```
***
## Complete Setup Workflow
### On Edge Device
```bash Quick Setup theme={null}
# One command to set up everything
m87 runtime start --email user@example.com
# Device is now:
# - Registered (pending approval)
# - Systemd service installed
# - Service enabled on boot
# - Service running
```
```bash Step-by-Step Setup theme={null}
# 1. Login/register
m87 runtime login --email user@example.com
# 2. Install and start service
m87 runtime start
# 3. Check status
m87 runtime status
```
### On Workstation
```bash theme={null}
# 1. List devices (shows pending)
m87 devices list
# 2. Approve device
m87 devices approve my-edge-device
# 3. Access device
m87 my-edge-device shell
```
***
## Service Management
### The runtime service:
* **Runs as your user** (not root)
* **Managed by systemd** (start/stop/restart)
* **Auto-starts on boot** (when enabled)
* **Handles privilege escalation** with sudo when needed
* **Logs to journald** (view with `journalctl -u m87-runtime`)
### View Service Logs
```bash theme={null}
# View recent logs
journalctl -u m87-runtime -n 50
# Follow logs in real-time
journalctl -u m87-runtime -f
# View logs from today
journalctl -u m87-runtime --since today
```
***
## Troubleshooting
### Service Won't Start
```bash Check Status theme={null}
# View detailed status
m87 runtime status
# View logs
journalctl -u m87-runtime -n 100
```
```bash Common Fixes theme={null}
# Restart service
m87 runtime restart
# Re-login if credentials expired
m87 runtime logout
m87 runtime login --email user@example.com
# Check systemd service
systemctl status m87-runtime
```
### Device Unreachable
```bash theme={null}
# On the edge device, check if runtime is running
m87 runtime status
# If stopped, start it
m87 runtime start
# Check network connectivity
ping make87.com
# View runtime logs for errors
journalctl -u m87-runtime -f
```
### Permission Errors
```bash theme={null}
# Ensure user is in correct groups
groups
# Re-login to apply group changes
# (or reboot)
```
***
## Command Behavior Summary
### Start vs Enable
* **`start`**: Enable on boot + start now
* **`enable`**: Only enable on boot (doesn't start now)
* **`enable --now`**: Same as `start`
### Stop vs Disable
* **`stop`**: Stop now (keeps enabled on boot)
* **`disable`**: Only disable on boot (keeps running now)
* **`disable --now`**: Disable on boot + stop now
### Restart
* Restarts if running
* Starts if stopped
* Standard systemd behavior
***
## Best Practices
**Runtime management tips:**
1. **Always enable on boot** - Use `start` or `enable --now` for production
2. **Monitor logs** - Check `journalctl` after startup for issues
3. **Update regularly** - Keep runtime updated with `m87 update && m87 runtime restart`
4. **Use organizations** - Register devices under org IDs for better fleet management
5. **Document devices** - Keep track of device names and registration details
***
## Related Commands
* [Device Access](/commands/device-access) - Access devices after runtime is running
* [Deployments](/commands/deployments) - Deploy applications to runtime devices
# System architecture
Source: https://docs.make87.com/concepts/architecture
Understand how m87 components work together to provide secure remote device access
The m87 system consists of three main components that work together to provide secure, outbound-only access to distributed devices.
## Core components
### m87 CLI (client)
The command-line interface you run on your workstation to interact with remote devices.
**Primary functions:**
* Device management (list, approve, reject)
* Remote access (shell, exec, port forwarding)
* File operations (copy, sync)
* Container management (docker commands)
* Authentication (OAuth2 device flow)
**Implementation:**
* Written in Rust for performance and reliability
* Runs on Linux and macOS
* Located in `m87-client` package
* Licensed under Apache-2.0
The CLI stores credentials in `~/.config/m87/credentials.json` with `0o600` permissions for security.
### m87 runtime (device agent)
A long-running process that runs on edge devices to enable remote management.
**Primary functions:**
* Maintains outbound connection to m87 server
* Executes commands received from authorized users
* Streams logs and metrics
* Handles file transfer operations
* Manages container deployments
**Implementation:**
* Written in Rust (shares codebase with CLI)
* Linux-only (supports amd64 and arm64)
* Can run as systemd service for production use
* Located in `m87-client` package (runtime feature)
The runtime runs as your user (not root) and can be managed with:
```bash theme={null}
m87 runtime enable --now # Enable and start service
m87 runtime status # Check service status
```
### m87 server (relay)
The backend service that connects CLI users to device runtimes.
**Primary functions:**
* Device registration and approval workflow
* Authentication and authorization
* QUIC tunnel relay between users and devices
* WebTransport support for browser-based access
* REST API for device management
**Implementation:**
* Written in Rust
* Uses MongoDB for persistence
* QUIC-based tunneling (via Quinn library)
* Located in `m87-server` package
* Licensed under AGPL-3.0-or-later
**Ports:**
* `443` (or 8084): Runtime connections and tunnel traffic (TLS/QUIC)
* `8085`: REST API
The server can be self-hosted for on-premise deployments or used via the hosted make87 platform.
## Communication flow
### Initial device registration
```mermaid theme={null}
sequenceDiagram
participant D as Device Runtime
participant S as m87 Server
participant C as CLI User
D->>S: Registration request (with device info)
S->>S: Create pending request
S-->>D: Return request_id
D->>D: Poll for approval
C->>S: List pending requests
S-->>C: Return pending devices
C->>S: Approve device (request_id)
S->>S: Generate API key
S-->>D: Return API key on next poll
D->>D: Save credentials
D->>S: Establish QUIC tunnel
```
1. **Device initiates registration**: Runtime calls `m87 runtime run --email user@example.com`
2. **Server creates request**: Stores device info, generates unique `request_id`
3. **User reviews request**: Runs `m87 devices list` to see pending devices
4. **User approves device**: Runs `m87 devices approve `
5. **Server issues credentials**: Generates API key for device
6. **Device polls for approval**: Receives API key within timeout
7. **Runtime saves credentials**: Stores in `~/.config/m87/credentials.json`
8. **Persistent connection**: Device establishes QUIC tunnel to server
### Runtime tunnel connection
Once registered, devices maintain a persistent outbound connection:
```mermaid theme={null}
sequenceDiagram
participant D as Device Runtime
participant S as m87 Server
participant C as CLI User
D->>S: Establish QUIC connection (outbound)
S->>S: Store tunnel in RelayState
loop Heartbeat
D->>S: Keep connection alive
end
C->>S: Request shell on device
S->>S: Verify user authorization
S->>D: Forward shell request via tunnel
D->>D: Spawn shell process
D-->>S: Stream I/O
S-->>C: Stream I/O
```
All connections are outbound from the device. No inbound ports need to be opened on the device network.
### Command execution flow
When you run commands like `m87 shell`:
1. **CLI authenticates**: Gets OAuth2 token (refreshes if expired)
2. **CLI requests action**: Sends authenticated request to server API
3. **Server authorizes**: Verifies user has access to device (scope-based)
4. **Server relays**: Forwards request through device's QUIC tunnel
5. **Runtime executes**: Spawns process and captures I/O
6. **Bidirectional stream**: I/O flows through server back to CLI
## Shared components
### m87-shared
A Rust crate containing types and utilities used by both client and server:
* Device system information structures
* Protocol message definitions
* Common serialization formats
* Shared utility functions
This is an internal crate, not published separately. It ensures consistency between client and server implementations.
## Technology stack
### Core technologies
* **Rust 1.85+**: All components written in Rust for safety and performance
* **QUIC (Quinn)**: Low-latency, multiplexed tunneling protocol
* **Tokio**: Async runtime for handling concurrent connections
* **TLS/Rustls**: Secure communication without OpenSSL dependencies
* **MongoDB**: Server-side persistence for devices, users, and deployments
### Key libraries
| Library | Purpose |
| --------------- | --------------------------------- |
| `quinn` | QUIC protocol implementation |
| `tokio-rustls` | Async TLS connections |
| `openidconnect` | OAuth2 device flow authentication |
| `jsonwebtoken` | JWT validation for API requests |
| `reqwest` | HTTP client for API calls |
| `serde` | Serialization/deserialization |
## Deployment patterns
### Hosted platform
Use the managed make87 platform (default):
```bash theme={null}
# CLI automatically connects to platform
m87 login
# Runtime registers with platform
m87 runtime run --email you@example.com
```
### Self-hosted
Deploy your own m87 server:
```bash theme={null}
# Start server with Docker Compose
cd m87-server
docker compose up -d
# Configure CLI to use your server
export M87_API_URL=https://your-server.com
m87 login
```
* **MongoDB**: For storing devices, users, and state
* **TLS certificate**: For port 443 (runtime/tunnel traffic)
* **OAuth provider**: Auth0 or compatible OAuth2/OIDC provider
* **Environment variables**: See `m87-server/docker-compose.yml`
**Minimum resources:**
* 1 CPU core
* 512MB RAM
* 10GB storage
## Performance characteristics
### Connection overhead
* **Initial tunnel setup**: \~100-200ms (QUIC handshake)
* **Command latency**: \~5-20ms after tunnel established
* **Reconnection**: Automatic with exponential backoff
### Scalability
* **Single server**: 1000+ concurrent device connections
* **Horizontal scaling**: Multiple servers with load balancing
* **Connection multiplexing**: Multiple streams per QUIC connection
### Resource usage
**Runtime (per device):**
* Idle: \~5-10MB RAM
* Active command: +10-50MB depending on operation
* CPU: Minimal when idle, scales with workload
**Server (per 1000 devices):**
* RAM: \~500MB-1GB
* CPU: 1-2 cores
* Network: \~100KB/s idle, scales with active traffic
The QUIC protocol provides efficient multiplexing, allowing multiple operations simultaneously without additional overhead.
## Build configuration
The codebase uses Cargo workspace with optimized release profiles:
```toml theme={null}
[profile.release]
opt-level = 3 # Maximum optimization
lto = "fat" # Full link-time optimization
codegen-units = 1 # Better optimization
strip = true # Strip debug symbols
```
This produces minimal, performant binaries suitable for resource-constrained edge devices.
# Authentication and authorization
Source: https://docs.make87.com/concepts/authentication
How users and devices authenticate with m87, and how access control works
The m87 platform uses different authentication mechanisms for CLI users and devices, with a scope-based authorization model for access control.
## User authentication (CLI)
### OAuth2 device flow
CLI users authenticate using the OAuth2 device authorization flow, which is designed for devices without browsers:
```bash theme={null}
m87 login
```
**What happens:**
1. CLI requests device authorization from OAuth provider
2. Provider returns verification URL and user code
3. CLI displays: "Visit [https://auth.make87.com/activate](https://auth.make87.com/activate) and enter code: ABCD-EFGH"
4. User opens browser and completes authentication
5. CLI polls token endpoint until user approves
6. CLI receives access token and refresh token
7. Tokens stored in `~/.config/m87/credentials.json`
The device flow is perfect for CLIs because it doesn't require embedding a web server or handling redirects. Users authenticate in their regular browser with full security features.
### Token lifecycle
**Token structure:**
```rust theme={null}
pub struct OAuth2Token {
pub access_token: String, // Short-lived (typically 1 hour)
pub refresh_token: Option, // Long-lived (days/weeks)
pub expires_at: u64, // Unix timestamp
}
```
**Automatic refresh:**
The CLI automatically refreshes expired tokens:
```rust theme={null}
impl OAuth2Token {
pub async fn get_access_token(&mut self, issuer_url: &str, client_id: &str) -> Result {
if self.is_valid() {
Ok(self.access_token.clone())
} else {
self.refresh(issuer_url, client_id).await?;
Ok(self.access_token.clone())
}
}
}
```
Every command checks token validity and refreshes if needed, so you never need to manually re-authenticate unless:
* Refresh token expires
* You explicitly logout (`m87 logout`)
* Credentials file is deleted
**Process:**
1. Check if current access token is expired
2. If expired, use refresh token to get new access token
3. Update stored credentials with new tokens
4. If provider rotates refresh token, save new one
5. Retry original command with fresh token
**Error handling:**
* If refresh fails (invalid/expired refresh token): Prompt user to run `m87 login` again
* If network error: Retry with exponential backoff
* If auth server down: Show helpful error message
**Security:**
* HTTP client configured with no redirects (prevents SSRF)
* Tokens transmitted only over HTTPS
* Client uses PKCE if supported by provider
### OAuth2 configuration
For self-hosted deployments, configure OAuth settings:
**Server environment variables:**
```bash theme={null}
OAUTH_ISSUER=https://auth.make87.com/
OAUTH_AUDIENCE=https://auth.make87.com
```
**Client configuration:**
```bash theme={null}
# Built into binary, but can be overridden
export M87_AUTH_DOMAIN=https://auth.make87.com/
export M87_AUTH_CLIENT_ID=your_client_id
export M87_AUTH_AUDIENCE=https://auth.make87.com
```
When using custom OAuth providers, ensure they support the device authorization grant type (RFC 8628). Not all OAuth2 providers implement this flow.
## Device authentication
### Registration and approval workflow
Devices use an API key-based system with manual approval:
#### Step 1: Initiate registration
On the device:
```bash theme={null}
m87 runtime run --email admin@example.com
```
**Request payload:**
```rust theme={null}
pub struct DeviceAuthRequestBody {
pub device_info: DeviceSystemInfo,
pub owner_scope: String,
pub device_id: String,
}
pub struct DeviceSystemInfo {
pub hostname: String,
pub platform: String, // "linux"
pub architecture: String, // "x86_64", "aarch64"
pub os_version: String,
// Additional system details...
}
```
**Server response:**
```json theme={null}
{
"request_id": "req_a1b2c3d4e5f6"
}
```
The runtime displays:
```text theme={null}
Posted auth request. To approve, check request id req_a1b2c3d4e5f6 via cli or visit make87.com
```
#### Step 2: List pending requests
From your workstation:
```bash theme={null}
m87 devices list
```
Output shows pending devices:
```text theme={null}
PENDING DEVICES:
ID Hostname Platform Requested by
req_a1b2c3d4e5f6 rpi-living linux admin@example.com
```
#### Step 3: Approve device
```bash theme={null}
m87 devices approve req_a1b2c3d4e5f6
```
**What happens on the server:**
1. Validate approver has permission for requested owner scope
2. Generate cryptographically secure API key
3. Store API key hashed in database
4. Mark request as approved
5. Return API key to polling runtime
#### Step 4: Device receives credentials
The runtime polls every 10 seconds:
```rust theme={null}
pub async fn wait_for_approval(&self, timeout: Duration) -> Result {
let start_time = Instant::now();
while start_time.elapsed() < timeout {
let res = server::check_auth_request(&self.api_url, request_id).await?;
if let Some(api_key) = res.api_key {
return Ok(api_key);
}
tokio::time::sleep(Duration::from_secs(10)).await;
}
Err(anyhow!("API key not approved within timeout"))
}
```
Once approved, the device:
1. Saves API key to `~/.config/m87/credentials.json`
2. Establishes QUIC tunnel to server
3. Becomes available for remote access
The approval timeout is 60 minutes by default. If it expires, simply run `m87 runtime run` again to create a new registration request.
### API key storage
Device credentials stored separately from user credentials:
```json theme={null}
{
"credentials": {
"OAuth2Token": { /* user credentials */ }
},
"device_credentials": {
"api_key": "m87_dev_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6"
}
}
```
**File security:**
* Permissions: `0o600` (owner read/write only)
* Location: `~/.config/m87/credentials.json`
* Format: JSON with pretty printing
### Environment variable authentication
For automation and CI/CD, provide credentials via environment:
```bash theme={null}
# Device API key
export M87_API_KEY=m87_dev_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
m87 runtime run
# Owner reference for registration
export OWNER_REFERENCE=admin@example.com
m87 runtime run
```
1. **Environment variables** (highest priority)
* `M87_API_KEY`: Device API key
* `OWNER_REFERENCE`: Owner scope for registration
2. **Config file**: `~/.config/m87/credentials.json`
3. **Interactive prompts** (lowest priority)
* Registration prompts for owner if not set
* Login prompts for OAuth if no credentials
## Authorization and access control
### Scope-based model
Access control uses a flexible scope system:
**Scope formats:**
* `user:`: Personal ownership (e.g., `user:alice@example.com`)
* `org:`: Organization ownership (e.g., `org:acme-corp`)
**Device ownership:**
```rust theme={null}
pub struct Device {
pub device_id: String,
pub owner_scope: String, // Who owns this device
pub allowed_scopes: Vec, // Who can access (optional)
// ...
}
```
**Access rules:**
1. **Owner access**: User's scope matches `owner_scope`
```
user:alice@example.com can access devices where owner_scope = "user:alice@example.com"
```
2. **Shared access**: User's scope in `allowed_scopes`
```
user:bob@example.com can access device if "user:bob@example.com" in allowed_scopes
```
3. **Organization access**: User belongs to organization
```
User with "org:acme-corp" scope can access all devices where:
- owner_scope = "org:acme-corp" OR
- allowed_scopes contains "org:acme-corp"
```
### Query filtering
The server automatically filters queries based on user scopes:
```rust theme={null}
impl AccessControlled for Device {
fn access_filter(scopes: &Vec) -> Document {
doc! {
"$or": [
{ "owner_scope": { "$in": scopes } },
{ "allowed_scopes": { "$in": scopes } }
]
}
}
}
```
**Example:**
User Alice (`user:alice@example.com`) runs `m87 devices list`:
1. Server extracts scopes from Alice's JWT: `["user:alice@example.com", "org:acme-corp"]`
2. Server queries MongoDB:
```javascript theme={null}
db.devices.find({
$or: [
{ owner_scope: { $in: ["user:alice@example.com", "org:acme-corp"] } },
{ allowed_scopes: { $in: ["user:alice@example.com", "org:acme-corp"] } }
]
})
```
3. Returns only devices Alice can access
This ensures users never see devices they don't have permission for, even if they guess device IDs.
Every API endpoint applies scope filtering, providing defense in depth even if application logic has bugs.
### Role-based permissions
Within organizations, roles control capabilities:
**Roles:**
* `admin`: Full access to all org devices, can approve registrations
* `member`: Access to assigned devices only
* `viewer`: Read-only access (logs, metrics, status)
**Implementation:**
```rust theme={null}
pub enum Role {
Admin,
Member,
Viewer,
}
impl Role {
pub fn can_approve_devices(&self) -> bool {
matches!(self, Role::Admin)
}
pub fn can_execute_commands(&self) -> bool {
matches!(self, Role::Admin | Role::Member)
}
}
```
Role enforcement happens server-side. The CLI cannot bypass these restrictions, even with a valid token.
## Authentication flows
### First-time setup (user)
```mermaid theme={null}
sequenceDiagram
participant U as User
participant C as m87 CLI
participant O as OAuth Provider
participant S as m87 Server
U->>C: m87 login
C->>O: Request device code
O-->>C: Return verification URL + code
C->>U: Display: Visit URL, enter code
U->>O: Authenticate in browser
O->>O: User approves device
loop Poll for token
C->>O: Check authorization status
end
O-->>C: Return access + refresh tokens
C->>C: Save to credentials.json
C->>U: "Logged in successfully"
```
### First-time setup (device)
```mermaid theme={null}
sequenceDiagram
participant D as Device
participant S as m87 Server
participant U as Admin User
D->>S: POST /auth/device (device info)
S->>S: Create pending request
S-->>D: Return request_id
D->>D: Display request_id
U->>S: GET /devices (list)
S-->>U: Show pending devices
U->>S: POST /devices/approve (request_id)
S->>S: Generate API key
loop Poll for approval
D->>S: GET /auth/device/status (request_id)
S-->>D: Status: pending
end
D->>S: GET /auth/device/status (request_id)
S-->>D: Status: approved, api_key
D->>D: Save credentials
D->>S: Establish QUIC tunnel
```
### Command execution (authenticated)
```mermaid theme={null}
sequenceDiagram
participant C as m87 CLI
participant S as m87 Server
participant D as Device Runtime
C->>C: Load OAuth token
C->>C: Refresh if expired
C->>S: POST /api/exec (JWT auth)
S->>S: Validate JWT
S->>S: Check user scopes vs device
S->>D: Forward request via QUIC
D->>D: Execute command
D-->>S: Stream output
S-->>C: Stream output
```
## Session management
### CLI sessions
**Login persistence:**
* OAuth tokens persist until refresh token expires
* Typical lifetime: 30 days (configurable by OAuth provider)
* Automatic refresh on every command
**Logout:**
```bash theme={null}
m87 logout
```
Removes credentials from local file but does not revoke tokens (follow OAuth provider's revocation process for that).
### Device sessions
**Persistent connection:**
* Device maintains long-lived QUIC tunnel
* Automatic reconnection on network changes
* Connection migration (QUIC feature) handles IP changes
**Deregistration:**
```bash theme={null}
# From CLI (removes device from platform)
m87 devices reject
# On device (clears local credentials)
m87 runtime logout
```
### Connection state
Server tracks active tunnels:
```rust theme={null}
pub struct RelayState {
tunnels: Arc>>,
lost: Arc>>,
}
```
**Tunnel lifecycle:**
1. Device establishes QUIC connection with API key in initial packet
2. Server validates API key, extracts device ID
3. Server stores tunnel in `RelayState`
4. CLI requests are routed through tunnel
5. On disconnect, server marks device as "lost"
6. On reconnect, server replaces old tunnel atomically
QUIC's connection migration feature allows devices to maintain sessions even when switching networks (e.g., Ethernet to WiFi).
## Security considerations
### Token security
**Access tokens:**
* Short-lived (default: 1 hour)
* Transmitted only over HTTPS
* Never logged or displayed
* Stored in memory, not written to disk between refreshes
**Refresh tokens:**
* Longer-lived (default: 30 days)
* Stored in credentials file with restrictive permissions
* Used only to obtain new access tokens
* Should be rotated periodically by OAuth provider
**API keys (devices):**
* Cryptographically random (256-bit entropy)
* Hashed before storage in database
* Transmitted only during initial approval
* Stored locally with file permissions
If a device's credentials file is compromised, an attacker gains access to that device. Immediately run `m87 devices reject ` to revoke access.
### Best practices
Periodically remove and re-register devices to rotate API keys, especially after personnel changes.
Register devices under `org:` scopes for team access rather than personal `user:` scopes.
Regularly review device audit logs (`m87 audit`) for unexpected access.
Never commit `~/.config/m87/credentials.json` to version control or share publicly.
## Troubleshooting authentication
### CLI login fails
**Symptoms:**
* "Failed to auth" error after entering code
* Token request times out
**Solutions:**
1. Check network connectivity to OAuth provider
2. Verify system clock is accurate (JWT validation requires correct time)
3. Try `m87 logout` then `m87 login` again
4. Check OAuth provider status page
### Device registration stuck
**Symptoms:**
* "Waiting for approval" never completes
* Request not visible in `m87 devices list`
**Solutions:**
1. Verify device can reach m87 server on port 443
2. Check request\_id matches between device and CLI
3. Ensure approving user has permission for requested owner scope
4. Look for firewall rules blocking outbound QUIC/UDP
### Token refresh fails
**Symptoms:**
* "Invalid token" errors after successful login
* Commands fail with authentication errors
**Solutions:**
1. Check refresh token hasn't expired: `m87 status`
2. Run `m87 logout && m87 login` to get fresh tokens
3. Verify OAuth provider hasn't revoked your tokens
4. Check credentials file permissions: `ls -la ~/.config/m87/credentials.json`
### Permission denied errors
**Symptoms:**
* "Device not found" for device you know exists
* "Access denied" when trying to access device
**Solutions:**
1. Verify your user scope matches device owner scope or is in allowed scopes
2. Check you're logged in with correct account: `m87 status`
3. Ask device owner to add your scope to `allowed_scopes`
4. For org devices, ensure you're a member of the correct organization
Enable debug logging to see authentication details: `RUST_LOG=debug m87 `
# Security model
Source: https://docs.make87.com/concepts/security
How m87 maintains security with outbound-only connections and zero trust architecture
The m87 security model is built around three core principles: outbound-only connectivity, zero trust architecture, and cryptographic authentication.
## Outbound-only access
One of m87's defining features is that devices never accept inbound connections.
### How it works
Devices initiate and maintain a persistent outbound QUIC connection to the m87 server:
```text theme={null}
Device (behind NAT/firewall)
└─[outbound only]──> m87 Server <──[authenticated]── CLI User
```
**Benefits:**
* No inbound ports to open on device networks
* Works through NATs, firewalls, and restrictive networks
* Reduces attack surface significantly
* Eliminates need for VPNs or dynamic DNS
Even though connections are outbound-only, m87 still provides full bidirectional communication for shells, file transfers, and port forwarding.
### Network requirements
For devices:
* Outbound TCP/UDP on port 443 to m87 server
* No inbound ports required
* No special firewall rules needed
For CLI users:
* HTTPS access to m87 server API (port 8085 or 443)
* Outbound connection for tunnel relay
The outbound-only design means you can deploy m87 on devices in cellular networks, behind carrier-grade NAT, or in locked-down enterprise environments.
## Authentication and authorization
### User authentication
m87 uses OAuth2 device flow for CLI users, integrating with standard identity providers:
**Flow:**
1. User runs `m87 login`
2. CLI initiates OAuth2 device authorization
3. User visits verification URL in browser
4. User enters device code and authenticates
5. CLI receives OAuth2 token (access + refresh)
6. Token stored locally in `~/.config/m87/credentials.json`
**Implementation details:**
```rust theme={null}
pub struct OAuth2Token {
pub access_token: String,
pub refresh_token: Option,
pub expires_at: u64, // Unix timestamp
}
```
Tokens are:
* Stored with `0o600` permissions (read/write for owner only)
* Automatically refreshed when expired
* Contain standard OAuth2 scopes: `openid`, `offline_access`, `email`, `profile`
**Security features:**
* No password handling in m87 (delegated to OAuth provider)
* Automatic token refresh using refresh tokens
* Tokens expire (typically 1 hour for access tokens)
* Supports Auth0 and standard OIDC providers
For CI/CD or automation, you can use API keys via the `M87_API_KEY` environment variable instead of interactive OAuth.
### Device authentication
Devices use an approval-based registration flow with API key credentials:
**Registration flow:**
1. Runtime runs `m87 runtime run --email admin@example.com`
2. Runtime sends registration request to server with:
* Device system information (hostname, platform, architecture)
* Requested owner scope (user email or org ID)
* Unique device ID (generated locally)
3. Server creates pending request with unique `request_id`
4. Admin approves via CLI: `m87 devices approve `
5. Server generates API key for device
6. Runtime polls server, receives API key
7. API key stored in `~/.config/m87/credentials.json`
**Device credentials:**
```rust theme={null}
pub struct APIKey {
api_key: String,
}
```
Device API keys provide persistent access. Treat them as sensitive credentials. If a device is compromised, immediately reject/remove it via `m87 devices reject `.
### Access control model
m87 uses a scope-based access control system:
**Scopes:**
* `user:`: Personal devices owned by a user
* `org:`: Organization-wide devices
**Access rules:**
* Users can access devices they own (`owner_scope` matches their user scope)
* Users can access devices shared with them (`allowed_scopes` includes their scope)
* Organization admins can access all org devices
**Implementation:**
```rust theme={null}
pub trait AccessControlled {
fn owner_scope_field() -> &'static str;
fn allowed_scopes_field() -> Option<&'static str>;
fn access_filter(scopes: &Vec) -> Document {
doc! {
"$or": [
{ Self::owner_scope_field(): { "$in": scopes } },
{ field: { "$in": scopes } }
]
}
}
}
```
Every API request validates that the authenticated user's scopes permit access to the requested device.
This scope-based model allows for flexible multi-tenancy while maintaining strong isolation between users and organizations.
## Transport security
### Encryption in transit
All communication uses industry-standard encryption:
**CLI ↔ Server:**
* HTTPS with TLS 1.2+ (via Rustls)
* Certificate validation enforced
* No legacy cipher suites
**Device ↔ Server:**
* QUIC with TLS 1.3 (via Quinn/Rustls)
* Certificate validation enforced
* Perfect forward secrecy
* 0-RTT resumption disabled (security over latency)
QUIC provides several security advantages over traditional TLS/TCP:
* **Built-in encryption**: All packets encrypted by default
* **Connection migration**: Handles network changes without re-authentication
* **Reduced handshake**: Faster establishment with equivalent security
* **Multiplexing**: Multiple streams without head-of-line blocking
QUIC uses TLS 1.3 internally, providing the same cryptographic guarantees as HTTPS.
### Certificate handling
By default, m87 validates server certificates against system root CAs:
```bash theme={null}
# Production use (validates certificates)
m87 login
m87 runtime run
```
For self-hosted development environments, you can disable validation:
```bash theme={null}
# Development only - trust invalid certificates
export M87_TRUST_INVALID_CERT=true
m87 login
```
Only use `M87_TRUST_INVALID_CERT=true` in controlled development environments. Production deployments should always use valid TLS certificates.
## Credential storage
### Local credential file
All credentials stored in `~/.config/m87/credentials.json`:
**Structure:**
```json theme={null}
{
"credentials": {
"OAuth2Token": {
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"expires_at": 1709500000
}
},
"device_credentials": {
"api_key": "m87_..."
}
}
```
**Security measures:**
* File permissions set to `0o600` (owner read/write only)
* Located in user config directory (not world-readable)
* Separate CLI and device credentials
* Parent directories created with restrictive permissions
### Environment variable support
For automation and CI/CD:
```bash theme={null}
# Device API key
export M87_API_KEY=m87_your_api_key_here
m87 runtime run # Uses key from environment
# Owner reference (for device registration)
export OWNER_REFERENCE=admin@example.com
m87 runtime run # Registers under this owner
```
When using environment variables in CI/CD, ensure they're stored in secure secret management systems (GitHub Secrets, Vault, etc.), not committed to repositories.
## Server-side security
### JWT validation
The m87 server validates all API requests using JWT tokens:
**Validation steps:**
1. Extract JWT from `Authorization: Bearer` header
2. Verify signature using JWKS from OAuth provider
3. Validate claims (issuer, audience, expiration)
4. Extract user scopes from token
5. Apply access control rules
**JWT claims structure:**
```rust theme={null}
pub struct Claims {
pub sub: String, // User ID
pub email: String, // User email
pub exp: u64, // Expiration timestamp
pub iss: String, // Issuer URL
pub aud: Vec, // Audience
// Custom claims for scopes/roles
}
```
### Connection state management
The server maintains secure tunnel state:
```rust theme={null}
pub struct RelayState {
tunnels: Arc>>,
lost: Arc>>,
}
```
**Security features:**
* Connection replacement: New connection closes old one atomically
* Stale connection protection: Tracks connection IDs to prevent race conditions
* Lost device detection: Marks devices as unavailable on disconnect
* Memory safety: Rust's ownership model prevents use-after-free bugs
The server uses `Arc>` for thread-safe shared state, ensuring consistent tunnel management even under high concurrency.
## Audit and logging
m87 provides comprehensive audit trails:
```bash theme={null}
# View who accessed your device
m87 audit --details
```
Audit logs capture:
* User identity and timestamp
* Actions performed (shell, exec, file access)
* Duration of sessions
* Source IP addresses
```rust theme={null}
pub struct AuditLog {
pub device_id: String,
pub user_id: String,
pub action: AuditAction,
pub timestamp: DateTime,
pub source_ip: Option,
pub session_duration: Option,
}
pub enum AuditAction {
Shell,
Exec { command: String },
FileAccess { path: String, operation: FileOp },
PortForward { ports: Vec },
}
```
## Security best practices
### For device operators
Review device information before approving registration requests. Verify hostname and system details match expected devices.
Register devices under organization IDs rather than personal emails for team access and better lifecycle management.
Regularly review device audit logs for unexpected access patterns or unauthorized actions.
Periodically remove and re-register devices to rotate API keys, especially after team member departures.
### For CLI users
Never commit `~/.config/m87/credentials.json` to version control. Add to `.gitignore` if working in repo.
Don't leave shell sessions open indefinitely. Exit when done to minimize exposure window.
Check device hostname and system info before running sensitive commands to ensure you're on the intended device.
Run `m87 logout` on shared or public machines to clear stored credentials.
### For self-hosted deployments
Always use certificates from trusted CAs (Let's Encrypt, etc.) for production servers.
Bind MongoDB to localhost or use network policies to prevent external access.
Configure a strong random secret for signing tunnel tokens in `docker-compose.yml`.
Set admin email list to control who can approve devices and manage the platform.
## Security disclosure
If you discover a security vulnerability in m87:
1. **Do not** open a public GitHub issue
2. Email [security@make87.com](mailto:security@make87.com) with details
3. Include steps to reproduce if possible
4. Allow time for patching before public disclosure
We take security seriously and will respond promptly to verified reports.
# Model Context Protocol Integration
Source: https://docs.make87.com/guides/mcp-integration
Integrate m87 with AI agents using the Model Context Protocol (MCP)
The m87 CLI includes a built-in [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server, enabling AI agents to interact with your edge devices and use all m87 platform commands as tools.
## Overview
The MCP integration exposes m87's functionality to AI agents, allowing them to:
* Query device status and metrics
* Execute commands on remote devices
* Manage deployments
* Forward ports
* Access logs and audit trails
* Transfer files
* And more
This enables natural language device management through AI assistants like Claude.
## Setup
Ensure m87 is installed and accessible in your PATH:
```bash theme={null}
which m87
```
If not installed, follow the [installation guide](/installation).
Add m87 to your MCP client configuration. The configuration varies by client:
### Claude Desktop / Claude Code
Edit your MCP settings file:
**macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
**Linux**: `~/.config/Claude/claude_desktop_config.json`
Add the following configuration:
```json theme={null}
{
"mcpServers": {
"m87": {
"type": "stdio",
"command": "m87",
"args": ["mcp"]
}
}
}
```
Ensure you're logged in to m87:
```bash theme={null}
m87 login
```
The MCP server will use your existing authentication.
Restart Claude Desktop or your MCP client to load the new configuration.
## Configuration Options
### Using Absolute Path
If `m87` is not in your PATH, specify the full path to the binary:
```json theme={null}
{
"mcpServers": {
"m87": {
"type": "stdio",
"command": "/usr/local/bin/m87",
"args": ["mcp"]
}
}
}
```
To find the full path:
```bash theme={null}
which m87
```
### Custom Environment Variables
You can pass environment variables to the MCP server:
```json theme={null}
{
"mcpServers": {
"m87": {
"type": "stdio",
"command": "m87",
"args": ["mcp"],
"env": {
"RUST_LOG": "debug"
}
}
}
}
```
## Usage Examples
Once configured, you can interact with your devices through natural language in your AI assistant:
### Device Management
```text Example Prompt theme={null}
"List all my connected devices"
```
```text Example Prompt theme={null}
"Show me the status of my production-server"
```
```text Example Prompt theme={null}
"Check the health and any incidents on device-123"
```
### Remote Execution
```text Example Prompt theme={null}
"Run 'systemctl status nginx' on production-server"
```
```text Example Prompt theme={null}
"Execute 'df -h' on all devices and summarize the disk usage"
```
### Logs and Monitoring
```text Example Prompt theme={null}
"Show me the latest logs from my edge-device-01"
```
```text Example Prompt theme={null}
"Get system metrics for production-server"
```
```text Example Prompt theme={null}
"Check who accessed my device in the last 24 hours"
```
### Deployments
```text Example Prompt theme={null}
"Show me the deployment status for production-server"
```
```text Example Prompt theme={null}
"What containers are running on edge-device-01?"
```
## Available MCP Tools
The m87 MCP server exposes all platform commands as tools, including:
* **Device Management**: List devices, check status, approve devices
* **Remote Access**: Execute commands, open shells, forward ports
* **File Operations**: Copy files, sync directories
* **Container Management**: Docker passthrough, view containers
* **Monitoring**: View logs, metrics, audit trails
* **Deployments**: Deploy compose files, check deployment status
* **Serial Access**: Forward serial mounts
The AI agent will automatically select and use the appropriate tools based on your requests.
## Supported MCP Clients
The m87 MCP server works with any MCP-compatible client:
* [Claude Desktop](https://claude.ai/download)
* [Claude Code (VS Code extension)](https://marketplace.visualstudio.com/items?itemName=Anthropic.claude-code)
* Any other MCP-compatible application
## Troubleshooting
* Verify the configuration file path is correct for your operating system
* Ensure the JSON syntax is valid
* Restart your MCP client after making changes
* Check that `m87` is accessible (run `which m87` or use absolute path)
Make sure you're logged in:
```bash theme={null}
m87 login
```
The MCP server uses your existing m87 credentials.
If you see errors about `m87` not being found:
1. Find the full path: `which m87`
2. Use the absolute path in your configuration:
```json theme={null}
{
"command": "/full/path/to/m87",
"args": ["mcp"]
}
```
You can test the MCP server directly:
```bash theme={null}
m87 mcp
```
This will start the server in stdio mode. Press Ctrl+C to exit.
## Security Considerations
* The MCP server uses your existing m87 authentication
* All operations are subject to your device permissions
* Audit logs track all device interactions, including those via MCP
* The MCP server runs with the same privileges as your user account
## Learn More
* [Model Context Protocol specification](https://modelcontextprotocol.io)
* [m87 CLI Reference](/api/cli-reference)
* [MCP Tools Reference](/api/mcp-tools)
# Self-Hosting m87 Server
Source: https://docs.make87.com/guides/self-hosting
Deploy and configure your own m87 server for on-premise device management
The m87 server can be self-hosted for on-premise deployments, giving you complete control over your device management infrastructure.
## Overview
m87-server handles device registration, authentication, and tunnel relay. It connects m87 runtimes on edge devices to m87 CLI users. Self-hosting allows you to:
* Keep all device data within your infrastructure
* Customize authentication and access control
* Meet compliance requirements
* Control updates and maintenance windows
## Requirements
* **MongoDB**: Version 8 or later (for device and user data storage)
* **Docker** (recommended): For containerized deployment
* **TLS Certificate**: For production deployments (Let's Encrypt or custom)
* **Public IP/Domain**: For device and client connectivity
## Quick Start with Docker Compose
```bash theme={null}
git clone https://github.com/make87/m87
cd m87/m87-server
```
Create a `.env` file with your configuration:
```bash theme={null}
cp .env.example .env
```
Edit the `.env` file with your settings (see [Configuration](#configuration) below).
```bash theme={null}
docker compose up -d
```
This will start:
* m87-server (API and tunnel relay)
* MongoDB (database)
* Watchtower (optional auto-updates)
Check that services are running:
```bash theme={null}
docker compose ps
docker compose logs -f m87-server
```
## Configuration
The m87 server is configured via environment variables. Below are the key settings:
### Core Database
MongoDB connection string used by m87-server
**Default**: `mongodb://mongo:27017`
```bash theme={null}
MONGO_URI=mongodb://mongo:27017
```
Logical database name used by m87-server
```bash theme={null}
MONGO_DB=m87-server
```
MongoDB root username (required for secured MongoDB setups)
```bash theme={null}
MONGO_INITDB_ROOT_USERNAME=admin
```
MongoDB root password (required for secured MongoDB setups)
```bash theme={null}
MONGO_INITDB_ROOT_PASSWORD=secure_password_here
```
### Authentication & OAuth
OAuth/OIDC issuer URL used to validate access tokens
**Default**: `https://auth.make87.com/`
```bash theme={null}
OAUTH_ISSUER=https://auth.make87.com/
```
For custom auth, use your Auth0 tenant or OIDC provider.
Expected OAuth audience for access tokens (must match the `aud` claim)
**Default**: `https://auth.make87.com`
```bash theme={null}
OAUTH_AUDIENCE=https://auth.make87.com
```
### Server Networking
Public base address where this server is reachable
**Default**: `localhost`
```bash theme={null}
PUBLIC_ADDRESS=m87.example.com
```
Used to check SNI of incoming requests for device ID prefixes.
Port for the unified public interface (runtime connections and tunnel traffic)
```bash theme={null}
UNIFIED_PORT=8084
```
This should be mapped to port 443 for TLS.
Port for the REST API
```bash theme={null}
REST_PORT=8085
```
Used for WebTransport endpoint for the web app (typically mapped to 8080).
### Admin & Security
Static admin API key for privileged actions
```bash theme={null}
ADMIN_KEY=your-secure-admin-key-here
```
Used for approving users, creating organizations, and bootstrapping admin access.
Change this from the default value in production!
Comma-separated list of email addresses that receive admin privileges
```bash theme={null}
ADMIN_EMAILS=admin@example.com,ops@example.com
```
### User Management
Whether newly registered users require manual approval
```bash theme={null}
USERS_NEED_APPROVAL=false
```
* `true`: User accounts start inactive until approved
* `false`: Users are active immediately
Domains that are auto-approved on signup
```bash theme={null}
USER_AUTO_ACCEPT_DOMAINS=make87.com,example.org
```
Comma-separated list (no spaces). If a user's email domain matches, approval is skipped.
### Device Sharing
Whether devices can be shared across different organizations
```bash theme={null}
ALLOW_CROSS_ORG_DEVICE_SHARING=false
```
* `true`: Cross-org device sharing allowed
* `false`: Devices are restricted to their organization
### Data Retention
Number of days audit log entries are retained
```bash theme={null}
AUDIT_RETENTION_DAYS=30
```
Older entries are automatically deleted.
Number of days deployment/report data is retained
```bash theme={null}
REPORT_RETENTION_DAYS=7
```
Older reports are automatically deleted.
### Other Settings
Whether the server runs in staging mode
```bash theme={null}
STAGING=0
```
* `0`: Production behavior
* `1`: Staging mode with relaxed checks and verbose logging
Logging level
```bash theme={null}
RUST_LOG=info
```
Options: `error`, `warn`, `info`, `debug`, `trace`
Path for TLS certificates
```bash theme={null}
CERTIFICATE_PATH=/data/m87/certs/
```
## Exposed Ports
The docker-compose configuration exposes:
* **443** → **8084** (TCP/UDP): Runtime connections and tunnel traffic (TLS)
* **8080** → **8085** (UDP): REST API and WebTransport endpoint
## Example Configuration
Here's a complete example `.env` file for production:
```bash theme={null}
# Database
MONGO_URI=mongodb://admin:secure_password@mongo:27017/admin
MONGO_DB=m87-server
MONGO_INITDB_ROOT_USERNAME=admin
MONGO_INITDB_ROOT_PASSWORD=secure_password
# Auth (using make87 default auth)
OAUTH_ISSUER=https://auth.make87.com/
OAUTH_AUDIENCE=https://auth.make87.com
# Networking
PUBLIC_ADDRESS=m87.example.com
UNIFIED_PORT=8084
REST_PORT=8085
# Security
ADMIN_KEY=change-this-to-secure-random-string
ADMIN_EMAILS=admin@example.com
# User management
USERS_NEED_APPROVAL=true
USER_AUTO_ACCEPT_DOMAINS=example.com
# Device sharing
ALLOW_CROSS_ORG_DEVICE_SHARING=false
# Retention
AUDIT_RETENTION_DAYS=90
REPORT_RETENTION_DAYS=14
# Environment
STAGING=0
RUST_LOG=info
```
## Building from Source
If you prefer to build the server binary yourself:
Requires Rust 1.85 or later:
```bash theme={null}
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
```
```bash theme={null}
git clone https://github.com/make87/m87
cd m87
cargo build --release -p m87-server
```
Binary will be at: `target/release/m87-server`
Ensure MongoDB is running, then:
```bash theme={null}
export MONGO_URI=mongodb://localhost:27017
export PUBLIC_ADDRESS=localhost
./target/release/m87-server
```
## Using Prebuilt Docker Images
Pull the latest image from GitHub Container Registry:
```bash theme={null}
docker pull ghcr.io/make87/m87-server:latest
```
Or specify a version:
```bash theme={null}
docker pull ghcr.io/make87/m87-server:v1.2.3
```
## Auto-Updates with Watchtower
The docker-compose file includes Watchtower for automatic updates:
```bash theme={null}
# Enable auto-updates profile
docker compose --profile auto-update up -d
```
Watchtower will:
* Check for new images every 5 minutes
* Automatically pull and restart with latest version
* Only update containers with the `watchtower.enable` label
## Production Deployment Checklist
* Set strong `MONGO_INITDB_ROOT_PASSWORD`
* Restrict MongoDB port access (only to m87-server)
* Enable MongoDB authentication
* Obtain TLS certificate (Let's Encrypt recommended)
* Configure reverse proxy (nginx, Caddy) or mount certificates
* Ensure port 443 routes to `UNIFIED_PORT`
* Change `ADMIN_KEY` from default
* Configure `ADMIN_EMAILS`
* Set `USERS_NEED_APPROVAL` based on your requirements
If using custom OAuth:
* Set up OAuth provider (Auth0, Keycloak, etc.)
* Update `OAUTH_ISSUER` and `OAUTH_AUDIENCE`
* Ensure tokens include required claims
* Update `PUBLIC_ADDRESS` to your domain
* Ensure DNS points to your server
* Verify firewall allows ports 443 and 8080
* Set appropriate `RUST_LOG` level
* Set up log aggregation
* Monitor container health
* Configure MongoDB backups
* Backup certificate files
* Store `.env` configuration securely
## Client Configuration
After deploying your server, configure clients to use it:
```bash theme={null}
# Set the server URL (if using custom server)
export M87_SERVER_URL=https://m87.example.com
# Login
m87 login
```
Clients will need to authenticate against your OAuth provider.
## Troubleshooting
Check logs:
```bash theme={null}
docker compose logs -f m87-server
```
Common issues:
* MongoDB connection failed (check `MONGO_URI`)
* Invalid OAuth configuration
* Port already in use
Verify:
* `PUBLIC_ADDRESS` is correct and DNS resolves
* Port 443 is accessible from internet
* TLS certificate is valid
* Firewall allows incoming connections
Check:
* `OAUTH_ISSUER` and `OAUTH_AUDIENCE` match your provider
* OAuth provider is reachable
* Tokens include required claims
* Admin key is correct
* Verify MongoDB is running: `docker compose ps mongo`
* Check MongoDB logs: `docker compose logs mongo`
* Ensure credentials in `.env` match MongoDB config
## Updating
### Manual Update
```bash theme={null}
# Pull latest image
docker compose pull
# Restart services
docker compose up -d
```
### With Watchtower (Automatic)
If running with the `auto-update` profile, Watchtower handles updates automatically.
## License
The m87 server is licensed under AGPL-3.0-or-later.
## Support
For issues and questions:
* [GitHub Issues](https://github.com/make87/m87/issues)
* [Documentation](https://docs.make87.com)
# SSH Integration
Source: https://docs.make87.com/guides/ssh-setup
Set up and use SSH integration with m87 for seamless device access
The m87 CLI includes built-in SSH integration, allowing you to use standard SSH clients to connect to your remote devices using the familiar `.m87` domain suffix.
## Overview
Once enabled, m87's SSH integration allows you to use native SSH commands to access your devices without needing to use the m87 CLI directly. This works with any SSH client and all SSH-based tools like `scp`, `rsync`, and `git`.
## Enable SSH Integration
Run the following command to enable SSH integration:
```bash theme={null}
m87 ssh enable
```
This configures your SSH client to route `.m87` domain connections through the m87 CLI.
Once enabled, you can connect to any device using SSH:
```bash theme={null}
ssh .m87
```
Replace `` with your device identifier (e.g., `my-device.m87`).
## Usage Examples
### Basic SSH Connection
```bash theme={null}
ssh production-server.m87
```
### Copy Files with SCP
```bash theme={null}
# Copy from device to local
scp production-server.m87:/var/log/app.log ./
# Copy from local to device
scp ./config.yml production-server.m87:/etc/app/
```
### Sync with Rsync
```bash theme={null}
# Sync local directory to device
rsync -avz ./src/ production-server.m87:/opt/app/src/
# Sync from device to local
rsync -avz production-server.m87:/var/backups/ ./backups/
```
### Execute Remote Commands
```bash theme={null}
# Run a single command
ssh production-server.m87 "systemctl status nginx"
# Run multiple commands
ssh production-server.m87 "cd /opt/app && git pull && systemctl restart app"
```
### Git over SSH
```bash theme={null}
# Clone a repository from a device
git clone production-server.m87:/opt/repos/myproject.git
# Add a device as a git remote
git remote add production ssh://production-server.m87/opt/repos/myproject.git
```
## How It Works
When you enable SSH integration, m87 configures your SSH client to:
1. Recognize `.m87` domain suffixes
2. Route these connections through the m87 CLI
3. Establish a secure tunnel to your device through the m87 platform
4. Forward the SSH session to the device
This allows you to use standard SSH tools while benefiting from m87's authentication and tunnel management.
## Compatibility
The SSH integration works with:
* Standard SSH clients (`ssh`, `openssh-client`)
* SCP and SFTP
* Rsync over SSH
* Git over SSH
* Any tool that uses SSH as a transport layer
## Troubleshooting
Ensure that:
* You're authenticated: run `m87 login`
* The device is online: check with `m87 devices list`
* SSH integration is enabled: run `m87 ssh enable` again
Verify the device name:
```bash theme={null}
m87 devices list
```
Use the exact device identifier from the list.
Make sure you have access to the device. Check your device permissions with:
```bash theme={null}
m87 devices list
```
## Related Commands
* [`m87 shell`](/commands/device-access) - Direct shell access without SSH
* [`m87 cp`](/commands/file-transfer) - Native m87 file copy
* [`m87 sync`](/commands/file-transfer) - Native m87 file sync
# Installation
Source: https://docs.make87.com/installation
Install m87 on your developer machine and edge devices
The m87 CLI can be installed on both your developer machine (for managing devices) and on edge devices (to run the runtime).
## System requirements
**Full support** (amd64, arm64)
CLI + runtime functionality
**CLI only** (amd64, arm64)
For managing remote devices
**Rust requirement for building from source:** Rust 1.85 or later
## Recommended installation
The fastest way to get started is using the one-line installer:
```bash theme={null}
curl -fsSL https://get.make87.com | sh
```
This installs the latest version to `$HOME/.local/bin`. Make sure this directory is in your `$PATH`:
```bash theme={null}
export PATH="$HOME/.local/bin:$PATH"
```
Add the export command to your shell configuration file (`~/.bashrc`, `~/.zshrc`, etc.) to make it permanent.
## Alternative installation methods
Download a pre-built binary from the [GitHub releases page](https://github.com/make87/m87/releases).
1. Download the appropriate binary for your platform
2. Extract the archive
3. Move the binary to a location in your `$PATH`:
```bash theme={null}
# Example for Linux
sudo mv m87 /usr/local/bin/
sudo chmod +x /usr/local/bin/m87
```
Build the binary yourself from the source code:
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
```
```bash theme={null}
cargo build --release
```
The binary will be created at `target/release/m87`.
```bash theme={null}
cp target/release/m87 $HOME/.local/bin/
# Or system-wide:
sudo cp target/release/m87 /usr/local/bin/
```
Build configuration is auto-detected by OS:
* **Linux:** Full functionality (CLI + runtime)
* **macOS:** CLI only
Run m87 from a container without installing anything locally. Useful for CI pipelines or keeping your system clean.
```bash theme={null}
git clone https://github.com/make87/m87.git
cd m87
docker build -f m87-client/Dockerfile -t m87 .
```
Configuration persists in `~/.config/m87`:
```bash theme={null}
docker run -it --rm \
--user "$(id -u):$(id -g)" \
-v "$HOME/.config/m87:/.config/m87" \
-e HOME=/ \
m87 login
```
For convenience, add an alias to your shell configuration:
```bash theme={null}
alias m87='docker run -it --rm --user "$(id -u):$(id -g)" -v "$HOME/.config/m87:/.config/m87" -e HOME=/ m87'
```
Now you can use `m87` commands directly:
```bash theme={null}
m87 devices list
m87 my-device shell
```
## Verify installation
Confirm that m87 is installed correctly:
```bash theme={null}
m87 version
```
You should see output showing the current version:
```text theme={null}
m87 [version]
```
## Updating m87
To update to the latest version:
```bash theme={null}
m87 update
```
This downloads and installs the latest m87 binary.
If you're running the m87 runtime on a device, you'll need to restart it after updating:
```bash theme={null}
m87 runtime restart
```
### Update a remote device
To update m87 on a remote device:
```bash theme={null}
m87 my-device exec -it -- 'm87 update && m87 runtime restart'
```
## Next steps
Connect your first device in under 5 minutes
# Introduction
Source: https://docs.make87.com/introduction
Secure, outbound-only access to physical devices with a native development experience
**m87** is make87's command line and device runtime for connecting to, debugging, and deploying software to distributed hardware fleets — all over a single outbound connection and without VPNs or inbound firewall rules.
## What is m87?
m87 provides secure, outbound-only access to physical devices with a native-feeling development, debugging, and software deployment experience. It consists of two components:
* **`m87` command** - The CLI you type in your terminal on your developer machine
* **m87 runtime** - The on-device process that maintains the outbound connection and executes actions
## What makes m87 different
m87 isn't just remote access — it's designed so working with real devices feels like local development and deployment:
Works behind NATs and firewalls without opening inbound ports or configuring VPNs
Shell, port forwarding, logs, and live debugging feel like you're working locally
One command line that transitions from access to orchestrating software deployments across fleets
If you've ever SSH'd into an embedded device only to run into network traps or scaling pain, m87 makes those workflows easy and repeatable.
## Core capabilities
### Development and debugging
Use native OS tools and IDEs as if the device were local:
```bash theme={null}
# Open an interactive shell
m87 my-device shell
# Forward a port for a debugging server
m87 my-device forward 8080:localhost:3000
# Run commands directly
m87 my-device exec -- htop
# Access Docker on the remote device
m87 my-device docker ps
```
### Software deployment
Deploy containers and services using familiar commands:
```bash theme={null}
# Deploy a Docker Compose stack
m87 my-device docker compose up -d
# Register async deployments for offline devices
m87 my-device deploy ./my-compose.yml
# Check deployment status
m87 my-device deployment status --logs
```
### File operations
Transfer and sync files with SCP and rsync-style commands:
```bash theme={null}
# Copy files to/from device
m87 cp ./local-file my-device:/remote/path
m87 cp my-device:/remote/file ./local-path
# Sync directories with watch mode
m87 sync --watch ./src my-device:/app/src
```
## Platform support
**Linux** (amd64, arm64) - Full functionality (CLI + runtime)
**macOS** (amd64, arm64) - CLI only (use for managing remote devices)
## Quick links
Get m87 installed on your developer machine and edge devices
Connect your first device in under 5 minutes
View source code, open issues, and contribute
Learn more about the make87 platform
## License
The m87 project is open source:
* **m87-client** and **m87-shared** - Apache-2.0
* **m87-server** - AGPL-3.0-or-later
# Quick start
Source: https://docs.make87.com/quickstart
Get up and running with m87 in 5 minutes
Get connected to your first edge device in just a few minutes. This guide walks you through installing m87, setting up a device, and running your first commands.
**Prerequisites:** m87 installed on your developer machine ([installation guide](/installation))
## Overview
You'll complete four steps:
1. Authenticate on your developer machine
2. Start the runtime on an edge device
3. Approve the device registration
4. Start using the device
Login to create your account. This opens your browser for OAuth authentication:
```bash theme={null}
m87 login
```
After successful authentication, your credentials are stored locally in `~/.config/m87/`.
On the edge device (e.g., Raspberry Pi, NVIDIA Jetson, server), install m87 and start the runtime:
```bash theme={null}
# Install m87 on the device
curl -fsSL https://get.make87.com | sh
# Start the runtime
m87 runtime run --email you@example.com
```
This registers the device and prints a request ID:
```
Device registration pending approval.
Request ID: req_abc123xyz
Waiting for approval...
```
The runtime waits for approval before connecting.
Back on your developer machine, approve the pending device:
```bash theme={null}
m87 devices approve req_abc123xyz
```
Or approve via the web UI at [make87.com/devices](https://make87.com/devices).
Once approved, the runtime on the edge device automatically connects and starts accepting commands.
List your devices:
```bash theme={null}
m87 devices list
```
You'll see output like:
```
NAME STATUS LAST SEEN
my-jetson-01 online 1m ago
```
Now you can interact with your device!
## Try these commands
Once your device is connected, try these common operations:
### Open a shell
Get an interactive shell on the remote device:
```bash theme={null}
m87 my-jetson-01 shell
```
Type `exit` or press `Ctrl+D` to close the shell.
### Execute a command
Run a single command:
```bash theme={null}
m87 my-jetson-01 exec -- uname -a
```
### Forward a port
Forward port 8080 from the remote device to your local machine:
```bash theme={null}
m87 my-jetson-01 forward 8080
```
Now access `http://localhost:8080` to reach the service running on the device.
### Check Docker containers
List running containers:
```bash theme={null}
m87 my-jetson-01 docker ps
```
### Copy files
Copy a file from your local machine to the device:
```bash theme={null}
m87 cp ./myapp my-jetson-01:/home/user/myapp
```
Or copy from the device to your local machine:
```bash theme={null}
m87 cp my-jetson-01:/var/log/app.log ./app.log
```
### Sync directories
Sync a local directory to the device (rsync-style):
```bash theme={null}
m87 sync ./src my-jetson-01:/app/src
```
Add `--watch` to continuously sync on file changes:
```bash theme={null}
m87 sync --watch ./src my-jetson-01:/app/src
```
## Set up runtime as a service
To keep the runtime running persistently on your edge device, set it up as a systemd service:
```bash theme={null}
# On the edge device
m87 runtime start
```
This enables the runtime to start automatically on boot.
The runtime service runs as your user (not root) and automatically restarts on failure.
Use `m87 runtime status` to check the service status or `m87 runtime stop` to stop it.
## What's next?
Forward ports, sockets, and serial devices
Copy and sync files with remote devices
Deploy containerized applications
Use standard SSH tools with m87
## Troubleshooting
### Device not appearing after approval
Wait a few seconds for the connection to establish. Check the runtime logs on the device:
```bash theme={null}
# On the device
m87 runtime status
```
### Authentication issues
Re-authenticate if your session expires:
```bash theme={null}
m87 logout
m87 login
```
### Can't connect to device
Ensure the runtime is running on the device:
```bash theme={null}
# On the device
m87 runtime status
```
If stopped, start it:
```bash theme={null}
# On the device
m87 runtime start
```
Report issues or ask questions on GitHub