Installing
Mailinois is one static binary with SQLite built in, so every way of running it is the same program with a config file. Pick whichever matches how you run everything else:
| How | Good for |
|---|---|
| Binary and systemd | A VM or a Postfix host, no container runtime. |
| Docker or Compose | Hosts that already run containers. |
| Kubernetes | Clusters, with the agent as a sidecar next to Postfix. |
| From source | Anything with Go 1.27. |
Binary and systemd
Section titled “Binary and systemd”Each release carries binaries for Linux (amd64 and arm64) and macOS (arm64), and a SHA256SUMS file. They are pure Go with SQLite built in, so there is nothing else to install.
version=v0.1.0arch=amd64 # or arm64base=https://github.com/SamJUK/mailinois/releases/download/$versioncurl -fLO $base/mailinois-linux-$archcurl -fLO $base/SHA256SUMSsha256sum --check --ignore-missing SHA256SUMSsudo install -m 755 mailinois-linux-$arch /usr/local/bin/mailinoisThen the config. Start from deploy/config/mailinois.example.yaml in the repository, keep what you need, and check it before starting anything:
sudo mkdir -p /etc/mailinoissudo curl -fLo /etc/mailinois/mailinois.yaml https://raw.githubusercontent.com/SamJUK/mailinois/$version/deploy/config/mailinois.example.yamlsudoedit /etc/mailinois/mailinois.yamlmailinois check -config /etc/mailinois/mailinois.yamlSecrets stay out of the config. Put them in /etc/mailinois/mailinois.env, mode 600, and refer to them in the YAML as ${NAME}:
sudo install -m 600 /dev/null /etc/mailinois/mailinois.envsudoedit /etc/mailinois/mailinois.env # MAILINOIS_PASSWORD=…, SLACK_WEBHOOK=…Install the unit from deploy/systemd/ and start it:
sudo curl -fLo /etc/systemd/system/mailinois.service https://raw.githubusercontent.com/SamJUK/mailinois/$version/deploy/systemd/mailinois.servicesudo systemctl daemon-reloadsudo systemctl enable --now mailinoiscurl -s http://127.0.0.1:7480/healthzWhat the unit does:
- It runs as a throwaway user (
DynamicUser=yes) that owns nothing but/var/lib/mailinois, where SQLite lives. The defaultsqlite:mailinois.dbis relative, so it lands there. - It reads
/etc/mailinois/mailinois.envif it exists, soMAILINOIS_PASSWORDand the${NAMES}in the config come from a file only root can read. - Everything else is locked down: a read-only system, no home directories, no capabilities, IPv4, IPv6 and Unix sockets only.
We ran both units on Debian 13, with systemd as PID 1 in a container. The server started and made its database in /var/lib/private/mailinois, and systemd-analyze security rated each unit 1.7, “OK”.
Importing old logs under systemd
Section titled “Importing old logs under systemd”mailinois import writes to the database itself, and under this unit the database belongs to the unit’s own user, so don’t run import as root against it. Send old logs through the API instead, as plain lines, with a token that has the ingest scope. A request takes up to 8 MiB, so split big files:
split -l 20000 /var/log/mail.log.1 /tmp/mail-part-for part in /tmp/mail-part-*; do curl -sS --data-binary @"$part" -H "Content-Type: text/plain" \ -H "Authorization: Bearer $INGEST_TOKEN" http://127.0.0.1:7480/api/v1/ingestdonerm /tmp/mail-part-*Lines already stored are skipped, so sending a file twice changes nothing.
The agent on a Postfix host
Section titled “The agent on a Postfix host”When Mailinois runs somewhere else, the agent follows the mail log on each Postfix host and ships it over HTTPS (Sending logs has the details). Install the same binary, then mailinois-agent.service, and give it its settings:
sudo curl -fLo /etc/systemd/system/mailinois-agent.service https://raw.githubusercontent.com/SamJUK/mailinois/$version/deploy/systemd/mailinois-agent.servicesudo install -m 600 /dev/null /etc/mailinois/agent.envsudoedit /etc/mailinois/agent.envsudo systemctl daemon-reloadsudo systemctl enable --now mailinois-agentMAILINOIS_SERVER=https://mailinois.example.comMAILINOIS_TOKEN=mlns_… # a token with the ingest scopeMAILINOIS_AGENT_ARGS=-file /var/log/mail.log # or -syslog 127.0.0.1:5514, and -milter 127.0.0.1:8892The unit joins the adm group, which can read /var/log/mail.log on Debian and Ubuntu. In the same Debian 13 test, the agent read a root:adm, mode 640 log through it and its lines arrived; with the group taken away it logged permission denied and kept retrying. Elsewhere, such as RHEL’s root-only /var/log/maillog, override it:
sudo systemctl edit mailinois-agent # [Service] SupplementaryGroups=… for a group that can read the logThe agent starts at the end of the file, so lines written while the agent itself is stopped (or can’t read the log) are never sent. In that test, a message logged while the group was missing didn’t arrive once the group was back. Send that stretch of the log through the API; the lines Mailinois already has are skipped.
Docker
Section titled “Docker”The image (ghcr.io/samjuk/mailinois, for amd64 and arm64) is distroless, runs as a non-root user and keeps SQLite on /data:
docker run -d --name mailinois --restart unless-stopped \ -p 7480:7480 -p 5514:5514 -p 5514:5514/udp \ -v mailinois-data:/data \ -v "$PWD/mailinois.yaml":/etc/mailinois/mailinois.yaml:ro \ --env-file mailinois.env \ ghcr.io/samjuk/mailinois:v0.1.0 serve -config /etc/mailinois/mailinois.yamlPin a version rather than latest, so a restart never upgrades it behind your back.
- The image sets
MAILINOIS_DATABASE_URL=sqlite:/data/mailinois.db, and environment variables win over the config file. For PostgreSQL, setMAILINOIS_DATABASE_URLto its URL rather thandatabase.url. - The image has no health check of its own.
mailinois healthcheckasks/healthzand fails unless it answers 200: use it as the Compose file below does, with-urlif the dashboard listens anywhere but127.0.0.1:7480inside the container.
Docker Compose
Section titled “Docker Compose”services: mailinois: image: ghcr.io/samjuk/mailinois:v0.1.0 restart: unless-stopped command: ["serve", "-config", "/etc/mailinois/mailinois.yaml"] env_file: mailinois.env ports: - "7480:7480" # dashboard, REST API, /metrics - "5514:5514" # syslog, TCP - "5514:5514/udp" # syslog, UDP volumes: - mailinois-data:/data - ./mailinois.yaml:/etc/mailinois/mailinois.yaml:ro healthcheck: test: ["CMD", "/mailinois", "healthcheck"] interval: 30s
volumes: mailinois-data:Publish 5514 only where your Postfix hosts can reach it and nobody else can: syslog has no authentication. If Postfix runs in the same Compose project, leave the syslog ports unpublished and point its rsyslog at mailinois on the project network instead.
From source
Section titled “From source”go install github.com/SamJUK/mailinois/cmd/mailinois@latestOr make build in a checkout, which writes bin/mailinois. It needs Go 1.27, and no C compiler.
Upgrading
Section titled “Upgrading”Dashboard CSS, JavaScript and logo URLs include a hash of their content. Browsers and caching proxies fetch the matching assets when an upgrade changes them, without a cache purge.
Replace the binary or the image tag and restart. Mailinois updates its database schema when it starts, so there is no separate migration step.
Back up first. For SQLite, stop Mailinois and copy the database file and its -wal file, or take a copy while it runs with sqlite3 mailinois.db ".backup backup.db". For PostgreSQL, pg_dump as usual.