Skip to content

Manual Installation

Install Mantis components directly on servers without containers.

SoftwareVersionPurpose
RustLatest stable (build images pin 1.93.x)Building components
Node.js20+Building Lens UI
npm10+Node package manager
PostgreSQL16+Database
RabbitMQ3.12+Message queue
Redis7.0+Cache and coordination
ResourceMinimumRecommended
CPU4 cores8+ cores
RAM8 GB16+ GB
Storage50 GB SSD100+ GB SSD
Terminal window
git clone https://github.com/MantisPlatform/mantis.git
cd mantis
Terminal window
# Install Rust if needed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
# Build all components in release mode
just build-release
# Or build individually
cd mandible && cargo build --release
cd ../thorax && cargo build --release
cd ../tarsus && cargo build --release

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:

Terminal window
# Build the SPA (produces lens/ui/build)
npm --prefix lens/ui ci
npm --prefix lens/ui run build
# Compile the binary with the UI embedded
cargo build --release -p lens --features lens/embed-ui
Terminal window
# Create directories
sudo mkdir -p /usr/local/bin
sudo mkdir -p /etc/mantis
sudo mkdir -p /var/lib/mantis
sudo mkdir -p /var/log/mantis
# Install binaries
sudo cp target/release/mandible /usr/local/bin/
sudo cp target/release/thorax /usr/local/bin/
sudo cp target/release/tarsus /usr/local/bin/
# Set permissions
sudo chmod 755 /usr/local/bin/mandible
sudo chmod 755 /usr/local/bin/thorax
sudo chmod 755 /usr/local/bin/tarsus
Terminal window
# Ubuntu/Debian
sudo apt update
sudo apt install postgresql-16 postgresql-contrib
# RHEL/Rocky/Alma
sudo dnf install postgresql16-server postgresql16
sudo /usr/pgsql-16/bin/postgresql-16-setup initdb
Terminal window
# Start service
sudo systemctl enable postgresql
sudo systemctl start postgresql
# Create database and user
sudo -u postgres psql <<EOF
CREATE USER mantis WITH PASSWORD 'your-secure-password';
CREATE DATABASE mantis OWNER mantis;
GRANT ALL PRIVILEGES ON DATABASE mantis TO mantis;
EOF
Terminal window
# Install diesel CLI
cargo install diesel_cli --no-default-features --features postgres
# Run migrations from the instinct directory
cd /path/to/mantis/instinct
export DATABASE_URL="postgres://mantis:your-secure-password@localhost/mantis"
diesel migration run
Terminal window
# Ubuntu/Debian (using official repository)
curl -1sLf 'https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey' | sudo gpg --dearmor -o /usr/share/keyrings/rabbitmq.gpg
echo "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.list
sudo apt update
sudo apt install rabbitmq-server
# RHEL/Rocky/Alma
sudo dnf install rabbitmq-server
Terminal window
# Enable and start
sudo systemctl enable rabbitmq-server
sudo systemctl start rabbitmq-server
# Enable management plugin
sudo rabbitmq-plugins enable rabbitmq_management
# Create user
sudo rabbitmqctl add_user mantis your-secure-password
sudo rabbitmqctl set_permissions -p / mantis ".*" ".*" ".*"
sudo rabbitmqctl set_user_tags mantis administrator

Create /etc/rabbitmq/rabbitmq.conf:

listeners.ssl.default = 5671
ssl_options.cacertfile = /etc/rabbitmq/certs/ca.crt
ssl_options.certfile = /etc/rabbitmq/certs/server.crt
ssl_options.keyfile = /etc/rabbitmq/certs/server.key
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = false
Terminal window
# Ubuntu/Debian
sudo apt install redis-server
# RHEL/Rocky/Alma
sudo dnf install redis

Edit /etc/redis/redis.conf:

bind 127.0.0.1
port 6379
requirepass your-secure-password
maxmemory 1gb
maxmemory-policy allkeys-lru
Terminal window
sudo systemctl enable redis
sudo systemctl start redis

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"

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).
[mandible]
endpoint = "localhost:50052"
[queue]
enabled = true
host = "localhost"
port = 5671
username = "mantis"
password = "password"
[cluster]
enabled = true
redis_url = "redis://:password@localhost:6379"

Create /etc/tarsus/tarsus.toml:

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/tarsus/cert.pem"
key_path = "/etc/tarsus/key.pem"
ca_cert_path = "/etc/tarsus/ca.crt"
[registration]
heartbeat_interval_secs = 30

Create /etc/systemd/system/mandible.service:

[Unit]
Description=Mantis Mandible API Server
After=network.target postgresql.service
[Service]
Type=simple
User=mantis
Group=mantis
Environment="MANDIBLE_CONFIG=/etc/mantis/mandible.toml"
Environment="RUST_LOG=info"
EnvironmentFile=-/etc/mantis/mandible.env
ExecStart=/usr/local/bin/mandible
Restart=always
RestartSec=5
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/mantis /var/log/mantis
PrivateTmp=yes
[Install]
WantedBy=multi-user.target

Create /etc/systemd/system/thorax.service:

[Unit]
Description=Mantis Thorax Orchestration Server
After=network.target rabbitmq-server.service redis.service
[Service]
Type=simple
User=mantis
Group=mantis
Environment="THORAX_CONFIG=/etc/mantis/thorax.toml"
Environment="RUST_LOG=info"
EnvironmentFile=-/etc/mantis/thorax.env
ExecStart=/usr/local/bin/thorax
Restart=always
RestartSec=5
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/mantis /var/log/mantis
PrivateTmp=yes
[Install]
WantedBy=multi-user.target

Create /etc/systemd/system/tarsus.service:

[Unit]
Description=Mantis Tarsus Agent
After=network.target
[Service]
Type=simple
User=root
Environment="TARSUS_CONFIG=/etc/tarsus/tarsus.toml"
Environment="RUST_LOG=info"
EnvironmentFile=-/etc/tarsus/tarsus.env
ExecStart=/usr/local/bin/tarsus
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Terminal window
# Create mantis user
sudo useradd -r -s /sbin/nologin mantis
# Set ownership
sudo chown -R mantis:mantis /etc/mantis
sudo chown -R mantis:mantis /var/lib/mantis
sudo chown -R mantis:mantis /var/log/mantis
# Reload systemd
sudo systemctl daemon-reload
# Enable and start services
sudo systemctl enable mandible thorax
sudo systemctl start mandible thorax
# On target servers
sudo systemctl enable tarsus
sudo systemctl start tarsus

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.

Terminal window
# `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=8443
HOST=0.0.0.0
MANTIS_API_URL=https://mantis.example.com
TLS_CERT_PATH=/etc/mantis/certs/lens-cert.pem
TLS_KEY_PATH=/etc/mantis/certs/lens-key.pem
LENS_REQUIRE_TLS=1
EOF

Create /etc/systemd/system/lens.service:

[Unit]
Description=Mantis Lens — management UI
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=mantis
Group=mantis
EnvironmentFile=/etc/mantis/lens.env
ExecStart=/usr/local/bin/lens
Restart=always
RestartSec=5
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
[Install]
WantedBy=multi-user.target

Enable and start:

Terminal window
sudo systemctl daemon-reload
sudo systemctl enable --now lens

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:

Terminal window
sudo ln -s /etc/nginx/sites-available/lens /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Terminal window
# Create CA private key
openssl genrsa -out /etc/mantis/certs/ca.key 4096
# Create CA certificate
openssl 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"
Terminal window
# Mandible
openssl 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 \
-out /etc/mantis/certs/mandible.crt \
-days 365 -sha256
# Thorax
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 \
-out /etc/mantis/certs/thorax.crt \
-days 365 -sha256
Terminal window
chmod 600 /etc/mantis/certs/*.key
chmod 644 /etc/mantis/certs/*.crt
chown -R mantis:mantis /etc/mantis/certs
Terminal window
# Service status
sudo systemctl status mandible
sudo systemctl status thorax
# Health endpoints
curl -k https://localhost:3000/api/v1/health
# gRPC health
grpcurl -insecure localhost:50051 grpc.health.v1.Health/Check
Terminal window
# Mandible logs
journalctl -u mandible -f
# Thorax logs
journalctl -u thorax -f
# Tarsus logs (on target)
journalctl -u tarsus -f
Terminal window
# Database
psql -h localhost -U mantis -d mantis -c "SELECT 1"
# RabbitMQ
rabbitmqctl status
# Redis
redis-cli -a password ping
Terminal window
# Mandible API
sudo firewall-cmd --permanent --add-port=3000/tcp
# Thorax gRPC
sudo firewall-cmd --permanent --add-port=50051/tcp
# Reload
sudo firewall-cmd --reload
Terminal window
# Mandible API
iptables -A INPUT -p tcp --dport 3000 -j ACCEPT
# Thorax gRPC
iptables -A INPUT -p tcp --dport 50051 -j ACCEPT
  1. Check configuration syntax
  2. Verify file permissions
  3. Check logs: journalctl -u <service> -xe
  1. Verify PostgreSQL is running
  2. Check connection URL
  3. Test manual connection
  1. Verify certificate paths
  2. Check certificate validity
  3. Ensure CA certificate is correct