Manual Installation
Manual Installation
Section titled “Manual Installation”Install Mantis components directly on servers without containers.
Prerequisites
Section titled “Prerequisites”Build Requirements
Section titled “Build Requirements”| Software | Version | Purpose |
|---|---|---|
| Rust | Pinned channel 1.96.1 (rust-toolchain.toml; build images use -rust-1.96.1-bookworm) | Building components |
| Node.js | 24 (vite 8 needs ^20.19 || >=22.12; CI/images use 24 — Node 20.0–20.18 fail) | Building Lens UI |
| npm | 10+ | Node package manager |
| PostgreSQL | 16+ | Database |
| RabbitMQ | 3.12+ | Message queue |
| Redis | 7.0+ | Cache and coordination |
System Requirements
Section titled “System Requirements”| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 4 cores | 8+ cores |
| RAM | 8 GB | 16+ GB |
| Storage | 50 GB SSD | 100+ GB SSD |
Building from Source
Section titled “Building from Source”Clone Repository
Section titled “Clone Repository”git clone https://github.com/MantisPlatform/mantis.gitcd mantisBuild Rust Components
Section titled “Build Rust Components”# Install Rust if neededcurl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | shsource ~/.cargo/env
# Build all components in release modejust build-release
# Or build individuallycd mandible && cargo build --releasecd ../thorax && cargo build --releasecd ../tarsus && cargo build --releaseBuild Lens
Section titled “Build Lens”Lens is a Rust binary that embeds its web UI. Build the UI first (Node is a build-time-only dependency), then compile the binary with the UI embedded:
# Build the SPA (produces lens/ui/build)npm --prefix lens/ui cinpm --prefix lens/ui run build
# Compile the binary with the UI embeddedcargo build --release -p lens --features lens/embed-uiInstall Binaries
Section titled “Install Binaries”# Create directoriessudo mkdir -p /usr/local/binsudo mkdir -p /etc/mantissudo mkdir -p /var/lib/mantissudo mkdir -p /var/log/mantis
# Install binariesfor bin in mandible thorax tarsus mantisctl lens; do sudo install -m 0755 "target/release/$bin" /usr/local/bin/doneThe release set is five binaries: mandible, thorax, tarsus, mantisctl
(admin CLI) and lens (UI).
Database Setup
Section titled “Database Setup”Install PostgreSQL
Section titled “Install PostgreSQL”# Ubuntu/Debiansudo apt updatesudo apt install postgresql-16 postgresql-contrib
# RHEL/Rocky/Almasudo dnf install postgresql16-server postgresql16sudo /usr/pgsql-16/bin/postgresql-16-setup initdbConfigure PostgreSQL
Section titled “Configure PostgreSQL”# Start servicesudo systemctl enable postgresqlsudo systemctl start postgresql
# Create database and usersudo -u postgres psql <<EOFCREATE USER mantis WITH PASSWORD 'your-secure-password';CREATE DATABASE mantis OWNER mantis;GRANT ALL PRIVILEGES ON DATABASE mantis TO mantis;EOFRun Migrations
Section titled “Run Migrations”# Install diesel CLIcargo install diesel_cli --no-default-features --features postgres
# Run migrations from the instinct directorycd /path/to/mantis/instinctexport DATABASE_URL="postgres://mantis:your-secure-password@localhost/mantis"diesel migration runRabbitMQ Setup
Section titled “RabbitMQ Setup”Install RabbitMQ
Section titled “Install RabbitMQ”# Ubuntu/Debian (using official repository)curl -1sLf 'https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey' | sudo gpg --dearmor -o /usr/share/keyrings/rabbitmq.gpgecho "deb [signed-by=/usr/share/keyrings/rabbitmq.gpg] https://packagecloud.io/rabbitmq/rabbitmq-server/ubuntu/ jammy main" | sudo tee /etc/apt/sources.list.d/rabbitmq.listsudo apt updatesudo apt install rabbitmq-server
# RHEL/Rocky/Almasudo dnf install rabbitmq-serverConfigure RabbitMQ
Section titled “Configure RabbitMQ”# Enable and startsudo systemctl enable rabbitmq-serversudo systemctl start rabbitmq-server
# Enable management pluginsudo rabbitmq-plugins enable rabbitmq_management
# Create usersudo rabbitmqctl add_user mantis your-secure-passwordsudo rabbitmqctl set_permissions -p / mantis ".*" ".*" ".*"sudo rabbitmqctl set_user_tags mantis administratorEnable TLS
Section titled “Enable TLS”Create /etc/rabbitmq/rabbitmq.conf:
listeners.ssl.default = 5671ssl_options.cacertfile = /etc/rabbitmq/certs/ca.crtssl_options.certfile = /etc/rabbitmq/certs/server.crtssl_options.keyfile = /etc/rabbitmq/certs/server.keyssl_options.verify = verify_peerssl_options.fail_if_no_peer_cert = falseRedis Setup
Section titled “Redis Setup”Install Redis
Section titled “Install Redis”# Ubuntu/Debiansudo apt install redis-server
# RHEL/Rocky/Almasudo dnf install redisConfigure Redis
Section titled “Configure Redis”Edit /etc/redis/redis.conf:
bind 127.0.0.1port 6379requirepass your-secure-passwordmaxmemory 1gbmaxmemory-policy allkeys-lruStart Redis
Section titled “Start Redis”sudo systemctl enable redissudo systemctl start redisComponent Configuration
Section titled “Component Configuration”Mandible Configuration
Section titled “Mandible Configuration”Create /etc/mantis/mandible.toml:
[server]host = "0.0.0.0"port = 3000
[server.tls]cert_path = "/etc/mantis/certs/mandible.crt"key_path = "/etc/mantis/certs/mandible.key"
[database]url = "postgres://mantis:password@localhost/mantis"max_connections = 10
[jwt]algorithm = "HS256"# secret = "" # Set via MANDIBLE__JWT__SECRET env var (min 32 chars)# Generate with: openssl rand -base64 32
# For RS256 (asymmetric, optional):# algorithm = "RS256"# private_key_path = "/etc/mantis/jwt/private.pem"# public_key_path = "/etc/mantis/jwt/public.pem"
[grpc]orchestration_url = "https://localhost:50051"tls_ca_cert_path = "/etc/mantis/certs/ca.crt"# Dispatch to Thorax is mTLS with a dedicated client identity whose CN is# "mandible" -- the shared internal cert is not trusted, so tenant-scoped deploys# would 403 without these.tls_cert_path = "/etc/mantis/certs/dispatch-cert.pem"tls_key_path = "/etc/mantis/certs/dispatch-key.pem"Thorax Configuration
Section titled “Thorax Configuration”Create /etc/mantis/thorax.toml:
[tls]auth_mode = "thumbprint"cert_path = "/etc/mantis/certs/thorax.crt"key_path = "/etc/mantis/certs/thorax.key"ca_cert_path = "/etc/mantis/certs/ca.crt"
[grpc]address = "0.0.0.0"port = 50051
# Thorax has NO database — all persistence is owned by Mandible, reached over# gRPC. Set THORAX__MANDIBLE__ENDPOINT to Mandible's internal gRPC (host:50052).# The endpoint MUST be https:// (Thorax rejects any other scheme), and the# connection is mTLS -- all three client-cert paths are required.[mandible]endpoint = "https://localhost:50052"tls_ca_cert_path = "/etc/mantis/certs/ca.crt"tls_cert_path = "/etc/mantis/certs/thorax.crt"tls_key_path = "/etc/mantis/certs/thorax.key"
# RabbitMQ is OPTIONAL (off by default -- Thorax polls the database instead).# Only include this block to use it, and ca_cert_path is mandatory because# queue TLS is required; Thorax refuses to start without it.[queue]enabled = truehost = "localhost"port = 5671username = "mantis"password = "password"ca_cert_path = "/etc/mantis/certs/rabbitmq-ca.pem"
# Cluster mode is also opt-in; required only for coordinated multi-instance Thorax.[cluster]enabled = trueredis_url = "redis://:password@localhost:6379"Tarsus Configuration
Section titled “Tarsus Configuration”Create /etc/mantis/<instance>.toml (Tarsus searches ./tarsus.toml, /etc/mantis/client.toml, ~/.config/mantis/client.toml — /etc/mantis/certs/ is not a search path):
name = "server-name"mode = "listen"
# Tarsus registers with Mandible (internal gRPC port 50052), not Thorax.# Mandible assigns the appropriate Thorax instance during registration.[mandible]endpoint = "mandible.example.com:50052"
[tls]cert_path = "/etc/mantis/certs/tarsus-cert.pem"key_path = "/etc/mantis/certs/tarsus-key.pem"ca_cert_path = "/etc/mantis/certs/ca-cert.pem"
[registration]heartbeat_interval_secs = 30Systemd Services
Section titled “Systemd Services”Mandible Service
Section titled “Mandible Service”Migrations run as a separate one-shot unit so that serving replicas never race
to migrate. Create /etc/systemd/system/mandible-migrate.service:
[Unit]Description=Mantis Mandible DB migrationAfter=network.target postgresql.service
[Service]Type=oneshotUser=mantisGroup=mantis# ProtectHome=yes hides the user's HOME, but libpq probes ~/.postgresql for a# client cert; point HOME at the data dir so DB connect works.Environment="HOME=/var/lib/mantis"Environment="MANDIBLE_CONFIG=/etc/mantis/mandible.toml"EnvironmentFile=-/etc/mantis/mandible.envExecStart=/usr/local/bin/mandible --migrate-onlyProtectHome=yesReadWritePaths=/var/lib/mantisThen the serving unit, which depends on it and skips migration itself. Create
/etc/systemd/system/mandible.service:
[Unit]Description=Mantis Mandible API ServerAfter=network.target postgresql.service mandible-migrate.serviceRequires=mandible-migrate.service
[Service]Type=simpleUser=mantisGroup=mantisEnvironment="HOME=/var/lib/mantis"Environment="MANDIBLE_CONFIG=/etc/mantis/mandible.toml"Environment="MANDIBLE_SKIP_MIGRATE=1"Environment="RUST_LOG=info"EnvironmentFile=-/etc/mantis/mandible.envExecStart=/usr/local/bin/mandibleRestart=alwaysRestartSec=5
# Security hardeningNoNewPrivileges=yesProtectSystem=strictProtectHome=yesReadWritePaths=/var/lib/mantis /var/log/mantisPrivateTmp=yes
[Install]WantedBy=multi-user.targetThorax Service
Section titled “Thorax Service”Create /etc/systemd/system/thorax.service:
[Unit]Description=Mantis Thorax Orchestration ServerAfter=network.target rabbitmq-server.service redis.service
[Service]Type=simpleUser=mantisGroup=mantisEnvironment="THORAX_CONFIG=/etc/mantis/thorax.toml"Environment="RUST_LOG=info"EnvironmentFile=-/etc/mantis/thorax.envExecStart=/usr/local/bin/thoraxRestart=alwaysRestartSec=5
# Security hardeningNoNewPrivileges=yesProtectSystem=strictProtectHome=yesReadWritePaths=/var/lib/mantis /var/log/mantisPrivateTmp=yes
[Install]WantedBy=multi-user.targetTarsus Service
Section titled “Tarsus Service”Tarsus is a templated unit — one instance per agent, keyed by %i. Create
/etc/systemd/system/tarsus@.service:
[Unit]Description=Mantis Tarsus Agent (%i)After=network.target
[Service]Type=simpleUser=rootEnvironment="TARSUS_CONFIG=/etc/mantis/%i.toml"Environment="RUST_LOG=info"EnvironmentFile=-/etc/mantis/%i.envExecStart=/usr/bin/tarsusRestart=alwaysRestartSec=5
[Install]WantedBy=multi-user.targetStart an agent named web-01 with systemctl start tarsus@web-01, and read its
logs with journalctl -u tarsus@web-01 — journalctl -u tarsus matches nothing.
Enable Services
Section titled “Enable Services”# Create mantis usersudo useradd -r -s /sbin/nologin mantis
# Set ownershipsudo chown -R mantis:mantis /etc/mantissudo chown -R mantis:mantis /var/lib/mantissudo chown -R mantis:mantis /var/log/mantis
# Reload systemdsudo systemctl daemon-reload
# Enable and start servicessudo systemctl enable mandible thoraxsudo systemctl start mandible thorax
# On target serverssudo systemctl enable tarsus@<instance>sudo systemctl start tarsus@<instance>Lens Deployment
Section titled “Lens Deployment”Lens is a self-contained binary (lens) — no Node.js runtime. The same release
tarball that carries mandible/thorax/tarsus/mantisctl also carries lens;
it embeds the web UI and reads its Mandible origin at runtime (MANTIS_API_URL),
so one binary serves any environment.
Install the binary + systemd unit
Section titled “Install the binary + systemd unit”# `lens` is already in /usr/local/bin if you installed the release tarball above.# Otherwise copy it there:sudo install -m 0755 lens /usr/local/bin/lens
# Runtime config (EnvironmentFile). MANTIS_API_URL is injected into the served# shell + CSP at runtime; LENS_REQUIRE_TLS=1 fails fast if the certs are missing.sudo tee /etc/mantis/lens.env >/dev/null <<'EOF'PORT=8443HOST=0.0.0.0MANTIS_API_URL=https://mantis.example.comTLS_CERT_PATH=/etc/mantis/certs/lens-cert.pemTLS_KEY_PATH=/etc/mantis/certs/lens-key.pemLENS_REQUIRE_TLS=1EOFCreate /etc/systemd/system/lens.service:
[Unit]Description=Mantis Lens — management UIAfter=network-online.targetWants=network-online.target
[Service]Type=simpleUser=mantisGroup=mantisEnvironmentFile=/etc/mantis/lens.envExecStart=/usr/local/bin/lensRestart=alwaysRestartSec=5ProtectSystem=strictProtectHome=yesNoNewPrivileges=yes
[Install]WantedBy=multi-user.targetEnable and start:
sudo systemctl daemon-reloadsudo systemctl enable --now lensSame-origin reverse proxy (required)
Section titled “Same-origin reverse proxy (required)”Lens and Mandible must share an origin so the HTTP-only refresh-token cookie
is visible to both. Front them with a single nginx server that serves Lens and
proxies /api to Mandible on the same origin:
server { listen 443 ssl http2; server_name mantis.example.com;
ssl_certificate /etc/nginx/certs/mantis.crt; ssl_certificate_key /etc/nginx/certs/mantis.key;
# Lens — the binary serves the UI + its own CSP/security headers over TLS. location / { proxy_pass https://127.0.0.1:8443; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; }
# Mandible REST — same origin as Lens (shared refresh cookie). location /api { proxy_pass https://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }}Enable the site:
sudo ln -s /etc/nginx/sites-available/lens /etc/nginx/sites-enabled/sudo nginx -tsudo systemctl reload nginxCertificate Setup
Section titled “Certificate Setup”Generate CA
Section titled “Generate CA”Prefer mantisctl cert (or just dev-certs), which set the SANs and EKUs
rustls requires — hand-rolled openssl below must add subjectAltName and
extendedKeyUsage or the certs are rejected with NotValidForName (webpki
matches the SAN only, with no CN fallback). Only use raw openssl on air-gapped
hosts, with the extensions shown.
# Create CA private keyopenssl genrsa -out /etc/mantis/certs/ca.key 4096
# Create CA certificateopenssl req -x509 -new -nodes \ -key /etc/mantis/certs/ca.key \ -sha256 -days 3650 \ -out /etc/mantis/certs/ca.crt \ -subj "/CN=Mantis CA/O=Your Organization"Generate Server Certificates
Section titled “Generate Server Certificates”# SAN + EKU extension file (REQUIRED — rustls matches the SAN, enforces the EKU)cat > /etc/mantis/certs/server-ext.cnf <<'EOF'subjectAltName = DNS:mandible, DNS:mandible.example.comextendedKeyUsage = serverAuth, clientAuthEOF
# Mandibleopenssl req -new -nodes \ -keyout /etc/mantis/certs/mandible.key \ -out /etc/mantis/certs/mandible.csr \ -subj "/CN=mandible.example.com"
openssl x509 -req \ -in /etc/mantis/certs/mandible.csr \ -CA /etc/mantis/certs/ca.crt \ -CAkey /etc/mantis/certs/ca.key \ -CAcreateserial \ -extfile /etc/mantis/certs/server-ext.cnf \ -out /etc/mantis/certs/mandible.crt \ -days 365 -sha256
# Thorax (reuse server-ext.cnf; adjust the SAN to the Thorax hostname)openssl req -new -nodes \ -keyout /etc/mantis/certs/thorax.key \ -out /etc/mantis/certs/thorax.csr \ -subj "/CN=thorax.example.com"
openssl x509 -req \ -in /etc/mantis/certs/thorax.csr \ -CA /etc/mantis/certs/ca.crt \ -CAkey /etc/mantis/certs/ca.key \ -CAcreateserial \ -extfile /etc/mantis/certs/server-ext.cnf \ -out /etc/mantis/certs/thorax.crt \ -days 365 -sha256Set Permissions
Section titled “Set Permissions”chmod 600 /etc/mantis/certs/*.keychmod 644 /etc/mantis/certs/*.crtchown -R mantis:mantis /etc/mantis/certsVerification
Section titled “Verification”Check Services
Section titled “Check Services”# Service statussudo systemctl status mandiblesudo systemctl status thorax
# Health endpointscurl -k https://localhost:3000/api/v1/health
# gRPC healthgrpcurl -insecure localhost:50051 grpc.health.v1.Health/CheckCheck Logs
Section titled “Check Logs”# Mandible logsjournalctl -u mandible -f
# Thorax logsjournalctl -u thorax -f
# Tarsus logs (on target)journalctl -u tarsus -fTest Connectivity
Section titled “Test Connectivity”# Databasepsql -h localhost -U mantis -d mantis -c "SELECT 1"
# RabbitMQrabbitmqctl status
# Redisredis-cli -a password pingFirewall Configuration
Section titled “Firewall Configuration”Using firewalld
Section titled “Using firewalld”# Mandible APIsudo firewall-cmd --permanent --add-port=3000/tcp
# Thorax gRPCsudo firewall-cmd --permanent --add-port=50051/tcp
# Reloadsudo firewall-cmd --reloadUsing iptables
Section titled “Using iptables”# Mandible APIiptables -A INPUT -p tcp --dport 3000 -j ACCEPT
# Thorax gRPCiptables -A INPUT -p tcp --dport 50051 -j ACCEPTTroubleshooting
Section titled “Troubleshooting”Service Won’t Start
Section titled “Service Won’t Start”- Check configuration syntax
- Verify file permissions
- Check logs:
journalctl -u <service> -xe
Database Connection Failed
Section titled “Database Connection Failed”- Verify PostgreSQL is running
- Check connection URL
- Test manual connection
Certificate Errors
Section titled “Certificate Errors”- Verify certificate paths
- Check certificate validity
- Ensure CA certificate is correct
Next Steps
Section titled “Next Steps”- Upgrades - Upgrade procedures
- Security Overview - Security configuration
- Production Checklist - Production readiness
