Stargate Docker deployment¶
Prerequisites¶
Server Requirements:
- 4 CPU cores (recommended minimum)
- 8 GB RAM (recommended minimum)
- 30 GB storage (recommended minimum)
- Docker will be installed automatically if missing
- Ensure there is an internet connection on the machine where you are installing Stargate services
- Ensure traffic is properly configured to reach Stargate instance
Supported Linux Distributions:
- RHEL 8, 9 and 10 compatible distributions such as Alma Linux, Rocky Linux, Centos Stream
- Ubuntu 22 and 24
- Debian 11, 12 and 13
Inbound Network Access (firewall must allow):
| Port | Protocol | Purpose |
|---|---|---|
| 25 | TCP | SMTP - receiving mail from external servers |
| 8084 | TCP | HTTP - seal callback from remote sealer service |
| 19818 | TCP+UDP | WireGuard - encrypted tunnel for agent-to-agent communication |
Outbound Network Access (server must reach):
| Destination | Port | Purpose |
|---|---|---|
| registry.vereign.io | 443 | Docker image registry |
| mxengine-dev.k8s.vereign-cdn.com | 443 | Remote sealer service |
| smimekeys-ca-dev.k8s.vereign-cdn.com | 443 | S/MIME CA service |
| loki.infra.vereign-cdn.com | 443 | Log shipping (Promtail → Loki) |
| vereign-issuer.vrgnservices.eu | 443 | Issuer service |
| vereign-verifier.vrgnservices.eu | 4433 | Verifier service |
| Destination mail servers | 25 | Outbound mail delivery (via MX lookup) |
DNS Access:
- Server must be able to resolve DNS (MX, SPF, A records)
- Used for mail routing and SPF-based network allowlisting
Step 1: Configure Customer Settings¶
Before installation, create and fill in the customer configuration file:
# Copy the template
cp customer-config.example customer-config.sh
# Edit the config file
nano customer-config.sh
Required settings — you must fill these in:
| Setting | Description | Example |
|---|---|---|
SERVER_STATIC_IP | This server's real static public IP. Used to derive WireGuard tunnel address and MXEngine callback URL. | 203.0.113.10 |
CUSTOMER_NAME | Customer/organization name (used for identification, logging, and as default certificate organization). | Acme Corp |
DEPLOYMENT_NAME | Unique deployment identifier (used in log labels and Promtail hostname). | stargate-acme |
MAIL_DOMAINS | Mail relay domains, comma-separated for multiple. MX and SPF records are looked up from DNS. | example.com or example.com,example.org |
Auto-derived settings — leave empty unless you need to override:
| Setting | Derived from | Default |
|---|---|---|
MXENGINE_PUBLIC_ADDRESS | SERVER_STATIC_IP | http://<SERVER_STATIC_IP>:8084 |
CERT_DNS_NAMES | MAIL_DOMAINS + MAIL_HOSTNAME | example.com,mail.example.com |
CERT_ORGANIZATION | CUSTOMER_NAME | Acme Corp |
CERT_COMMON_NAME | CUSTOMER_NAME | Acme Corp Mail Signing |
MAIL_HOSTNAME | First domain in MAIL_DOMAINS | mail.example.com |
S/MIME certificate settings:
| Setting | Description | Default |
|---|---|---|
CERT_COUNTRIES | Country codes for certificate subject (2-letter ISO) | US |
CERT_CA_IDAGENT_DOMAIN | CA domain for certificate issuance via WireGuard tunnel | hintest.ch |
WireGuard peer settings (pre-filled for HIN Test):
The template comes with HIN Test peer defaults. These work out of the box for the alpha/beta phase. Override only if connecting to a different peer.
| Setting | Default (HIN Test) | Description |
|---|---|---|
WG_PEER_NAME | hin-test | Human-readable peer name |
WG_PEER_PUBLIC_KEY | ol2zlG40M7+Rn81V9RUFmkIQV2ILLmEJHZww7HfoLxA= | Remote peer's WireGuard public key |
WG_PEER_ENDPOINT | 5.102.144.182:19818 | Remote peer's public endpoint (host:port) |
WG_PEER_IP | 5.102.144.182 | Remote peer's WireGuard tunnel IP |
WG_PEER_PORT | 9090 | HTTP communication port on the remote peer |
WG_PEER_EXTERNAL_ID | hintest.ch | External identifier for routing (typically the peer's domain) |
Note:
WG_PEER_IPis the peer's tunnel address (used for routing inside WireGuard), whileWG_PEER_PORTis the HTTP port the peer's IDAgent listens on for API calls over the tunnel.
WireGuard local settings (typically left at defaults):
| Setting | Default | Description |
|---|---|---|
WG_PRIVATE_KEY | (auto-generated) | Generated by IDAgent on first run, then saved to customer-config.sh |
WG_LOCAL_IP | SERVER_STATIC_IP | Auto-derived. Only override if you need a different tunnel address. |
WG_INTERFACE_PORT | 19818 | WireGuard tunnel port (both TCP and UDP are exposed) |
WG_TRANSPORT_MODE | tcp | Transport protocol: tcp (default, works through most firewalls) or udp |
Optional settings (have sensible defaults):
| Setting | Default | Description |
|---|---|---|
POSTGRES_PASSWORD | (auto-generated) | Auto-generated 24-char random password if empty |
MINIO_ROOT_PASSWORD | (auto-generated) | Auto-generated 24-char random password if empty |
OUTBOUND_SEALER_MX_DOMAIN | hintest.ch | Sealer MX domain for outbound seal delivery |
POLICY_SYNC_REPO_URL | GitHub HIN Stargate policies | Git repo URL for OPA/Rego policy sync |
LOKI_URL | https://loki.infra.vereign-cdn.com | Loki endpoint for centralized log shipping |
Auto-generated (do not set manually):
VAULT_TOKEN— Generated by Vault during first initialization, saved tocustomer-config.shWG_PRIVATE_KEY— Generated by IDAgent on first run, saved tocustomer-config.sh
Step 2: Deploy to a Server¶
# Copy files to the server
scp -r docker-compose/* your-server:/path/to/stargate/
# SSH to server
ssh your-server
cd /path/to/stargate
# Create customer config from template
cp customer-config.example customer-config.sh
nano customer-config.sh # Fill in required settings (see Step 1)
# Run installation
chmod +x scripts/*.sh
./scripts/install.sh
Step 3: What Install Does¶
The install script (install.sh) performs the following steps:
- Check dependencies — Detects Docker, Docker Compose, and
jq. If missing, installs them automatically (supports Ubuntu/Debian, RHEL/AlmaLinux/Rocky). - Load and validate
customer-config.sh— Checks required fields (SERVER_STATIC_IP,CUSTOMER_NAME,DEPLOYMENT_NAME,MAIL_DOMAINS). Auto-derives optional fields (certificate names, MXEngine URL, etc.). - Generate
.envfrom customer config — Auto-generates passwords if not set. - Start all services via Docker Compose (infrastructure + applications).
- Initialize Vault — The
vault-initcontainer initializes, unseals, and creates KV-v2 secret mounts. Optionally writes the WireGuard private key to Vault. - Save Vault keys to
secrets/vault-keys.jsonand update.envwith the root token. The token is also saved tocustomer-config.shfor persistence across VM recreations. - Restart application services to pick up the Vault token.
- Run initial onboarding (
onboard.sh --initial-setup): - Generate S/MIME signing key and CSR (saved to
secrets/signing-key.csr) - Set up WireGuard peer connection in the database
- If the WireGuard tunnel to the CA is not yet established, CSR submission may fail — this is expected on first install. Services still run; retry with
./scripts/onboard.sh --regenerate-certonce the tunnel is up. - Save WireGuard private key to
customer-config.sh— extracted from Vault after IDAgent generates it. - Set up daily backup cron job (runs at 2:00 AM).
Step 4: Onboard Domains (Post-Install)¶
After initial installation, use onboard.sh to manage domains, certificates, and WireGuard peers:
# Edit customer-config.sh to add/change domains or peer settings
nano customer-config.sh
# Apply changes
./scripts/onboard.sh
What onboard.sh does:
- Loads and validates settings from
customer-config.sh - Auto-derives certificate fields (
CERT_DNS_NAMES,CERT_ORGANIZATION,CERT_COMMON_NAME) the same wayinstall.shdoes - Updates
.envwith current domain, certificate, and WireGuard settings - Generates S/MIME key + CSR (skips if already exists, use
--regenerate-certto force) - The CSR is submitted to the CA via the WireGuard tunnel (90-second timeout)
- If submission fails (tunnel not ready), a warning is printed and the script continues — services remain running
- Sets up or updates the WireGuard peer connection in the database (runs
idagent-init) - Restarts affected services (postfix-relay, mxengine, idagent)
Exit codes:
0— Everything succeeded1— Fatal error (missing config, etc.)2— Partial success (services running, but certificate issuance failed — retry later)
Adding a new domain:
- Edit
customer-config.sh— add domain toMAIL_DOMAINS(comma-separated):
- Run
./scripts/onboard.sh - The script updates Postfix routing, regenerates certificate SANs (if auto-derived), and restarts services
Regenerating certificates:
Step 5: WireGuard Peer Registration¶
After installation, the S/MIME certificate issuance will fail if your Stargate instance is not yet registered as a WireGuard peer on the HIN CA side. This is the most common issue during initial setup.
What you need to provide to HIN:
- WireGuard public key - extract from idagent logs:
DEPLOYMENT_NAME- from yourcustomer-config.shSERVER_STATIC_IP- the public IP of your Stargate serverWG_INTERFACE_PORT- only if you changed it from the default19818
Send these values to HIN so they can register your peer on the CA side.
After HIN confirms your peer is registered:
Restart the services and regenerate the certificate:
This restarts services (including idagent), which triggers a new WireGuard handshake. Since the CA now has your keys, the tunnel should establish and the certificate should be issued.
To verify the tunnel before requesting the certificate:
# Restart just idagent
docker compose restart idagent
# Check for successful WireGuard handshake
docker compose logs idagent 2>&1 | grep -i "handshake\|peer"
Also check your firewall: Port
19818/TCPmust be open both inbound and outbound on the Stargate server.
Step 6: Post-Onboarding Recommendations¶
Once the certificate is issued and mail is flowing, two configuration items are strongly recommended for any production deployment. Skipping them does not break encryption, but it will degrade your sender reputation, cause "we can't verify the sender" warnings in Outlook/Gmail, and can eventually lead to outbound mail being blocklisted.
6.1 SPF / DKIM / DMARC for sender domains¶
The Stargate sends notification mails (and S/MIME-encrypted mails) from its own public IP on behalf of your users. Recipients (Outlook, Gmail, Proofpoint, etc.) authenticate the sender by checking SPF, DKIM and DMARC against the From: domain. If the Stargate IP is not authorized for the sender's domain, recipients will show a yellow "we can't verify this email" banner and may flag the message as suspicious.
For each domain you route through the Stargate, publish the following DNS records:
SPF - authorize the Stargate IP. If your mailboxes also live in M365, keep the Microsoft include:
If you do not use M365 / Google Workspace, the minimal record is:
DMARC - publish at least a monitoring policy. This alone is enough to clear Outlook's "can't verify" banner once SPF passes:
Once you have confirmed alignment in the reports, you can tighten to p=quarantine and eventually p=reject.
DKIM - if example.ch is an accepted domain in your M365 or Google Workspace tenant, enable DKIM signing in the admin centre and publish the two selector1._domainkey / selector2._domainkey CNAMEs as instructed there. Publishing the CNAMEs is not enough on its own - DKIM signing must be toggled on in the tenant.
Verifying: send a test mail to a Gmail or Outlook account, open the message source / "View original", and look for the Authentication-Results: header. You want to see spf=pass, dkim=pass and dmarc=pass.
Useful tools:
- SPF / DNS lookup count: https://mxtoolbox.com/spf.aspx (the total
include:chain must stay under 10 lookups) - DMARC: https://mxtoolbox.com/dmarc.aspx
6.2 Relay outbound mail back through your mail platform (recommended for M365 / Exchange Online)¶
By default, after the Stargate signs/encrypts an outbound mail it delivers directly to the recipient's MX. This works, but the connecting IP is your Stargate's IP - and unless that IP has years of warm reputation, it can end up on third-party blocklists (e.g. Barracuda, abusix), causing intermittent delivery failures.
The recommended pattern is to send the signed mail back through your M365 / Exchange tenant so that the final hop to the internet is Microsoft's well-reputed infrastructure. The Stargate still signs and policy-checks every message; only the last hop changes. This mirrors the original HIN MGW "Send to MX" connector pattern.
Stargate side - per-domain relay¶
In customer-config.sh, point each domain's outbound back at its M365 inbound endpoint:
Apply:
After mxengine signs the mail, Postfix will hand it back to your tenant on port 25 with TLS instead of delivering directly to the recipient's MX. See Exchange-integration.md for the full per-domain syntax.
M365 / Exchange Online side¶
You essentially recreate the same connector + transport-rule set as the old HIN MGW (the original HIN MGW O365 manual is the reference - the same five rules apply). The minimum is:
- Inbound connector - accept mail from the Stargate, identified by TLS certificate (the cert subject must match a domain accepted in your tenant). A self-signed cert on the Stargate will be rejected by this connector - use a valid CA-issued cert (Let's Encrypt is fine).
- Outbound connector "Send to MX" - delivers to the recipient's MX, activated only by transport rule.
- Transport rule
set_header- tags outbound mail with a header likeoutgoing: outgoing_<domain>before it leaves O365 the first time, so the return trip can recognise it. - Transport rule
outgoing_to_mx- matches theoutgoing_<domain>header on mail coming back from the Stargate and routes it via the "Send to MX" connector. - Transport rule
mgw_bypass_antispam- bypasses spam filtering on mail coming back from the Stargate.
mxengine does not strip arbitrary headers, so the outgoing_<domain> tag set by set_header survives the round-trip and triggers outgoing_to_mx correctly.
Why this pattern matters: with the relay-back configuration, the public sender to the internet is Microsoft. Combined with correct SPF/DKIM/DMARC (section 6.1), recipients see a Microsoft IP with
spf=passanddkim=passaligned to your domain - which is the cleanest reputation profile you can give them.
See Exchange-integration.md for full step-by-step instructions including screenshots.
Subsequent Starts (after reboot)¶
The start script:
- Starts infrastructure services
- Unseals Vault using stored keys
- Starts application services
Stop Services¶
This stops containers but preserves all data.
Data Persistence¶
All data is stored in Docker volumes and persists across restarts.
| Service | Volume | Data |
|---|---|---|
| PostgreSQL | postgres_data | All databases (smimekeys, policy, idagent, mxengine) |
| Vault | vault_data | Encryption keys, secrets, S/MIME keys |
| MinIO | minio_data | Object storage (messages, attachments) |
| Postfix | postfix_spool | Mail queue |
Safe Operations (data preserved)¶
# Stop and start - data safe
./scripts/stop.sh
./scripts/start.sh
# Or using docker compose directly
docker compose down # Stops containers, KEEPS volumes
docker compose up -d # Restarts containers
./scripts/start.sh # Unseals Vault
Vault Sealing Behavior¶
Vault becomes sealed when its container restarts. This is a security feature.
After any restart, run ./scripts/start.sh to unseal Vault. The script uses the keys stored in secrets/vault-keys.json.
Destructive Operations (data deleted)¶
These commands DELETE ALL DATA - use with caution:
# Delete everything (volumes, secrets, config)
./scripts/stop.sh --purge
# Or manually remove volumes
docker compose down -v # The -v flag removes volumes!
Scripts Reference¶
| Script | Purpose |
|---|---|
install.sh | First-time installation (Docker, Vault, then calls onboard.sh) |
onboard.sh | Domain onboarding (S/MIME key, WireGuard peer, mail domains, service restart) |
start.sh | Start services and unseal Vault |
stop.sh | Stop containers (data preserved) |
backup.sh | Full backup (database, Vault keys, config, certificates) |
restore.sh | Restore from backup archive (works on fresh machine) |
purge.sh | Delete ALL data (requires confirmation) |
health-check.sh | Comprehensive health check of all services (exit 0 = healthy, 1 = failures) |
init-vault.sh | Vault initialization (used by vault-init container, not called directly) |
init-idagent.sh | WireGuard peer connection setup (used by idagent-init container, not called directly) |
gather-app-versions.sh | Collects app versions from /liveness endpoints for node-exporter (runs in version-collector container) |
Configuration Files¶
| File | Purpose |
|---|---|
customer-config.example | Template for customer settings (copy to customer-config.sh) |
customer-config.sh | Customer-specific settings (created from template, fill in before install) |
.env | Generated environment file (created by install.sh, updated by onboard.sh) |
secrets/vault-keys.json | Vault unseal keys and root token (back up securely!) |
secrets/signing-key.csr | Generated CSR for S/MIME certificate |