GuidesBusiness and monitoringTeam chat with Mattermost

Run Mattermost team chat with rootless Podman

Mattermost Team Edition on Debian 13, a self-hosted chat for your company with channels, direct messages, threads and file sharing, in rootless Podman with PostgreSQL under a user of its own behind Caddy, with the first admin created before it goes online.

Tested on Mattermost 11.11.1 on Debian 13 (trixie) on a Melonslab server Updated October 14, 2026

Recommended server for this guide

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy · 2 vCPU · 8 GB Memory · 60 GB Storage

Month to month, no lock-in 7-day money-back guarantee

€8.99/mo

Deploy now
On this page

What you will set up

Mattermost is a chat for a company or a team, much like Slack: public and private channels, direct and group messages, threads, reactions, file sharing and search. It has apps for Windows, macOS, Linux, Android and iOS as well as the web, and webhooks and bots to connect it to other systems.

Mattermost is made by Mattermost, Inc., a company in Palo Alto, California, and developed in the open on GitHub. Team Edition is its free, open-source edition, the one this guide uses; the paid editions add features such as single sign-on and compliance exports. A server without a licence takes up to 250 active users, and warns from 200.

Here Mattermost runs as a pod under a user of its own called mattermost, with PostgreSQL for its data, behind Caddy from the Podman guide.

Every step below was run on a fresh Melonslab VC-P Alloy (2 vCPU, 8 GB) with Debian 13:

  • Mattermost 11.11.1 started with PostgreSQL 17. The first admin and a team were created from the command line before Caddy was set up, and a sign-up from outside without an invite was refused.
  • A second user joined through the team's invite link. A message from one user appeared in the other's open browser within a fraction of a second, without a reload, over the websocket through Caddy.
  • A 300 KB file and a 90 MB file were uploaded through Caddy and downloaded intact by the other user, and an image got a preview. A 110 MB file was refused with the size limit.
  • Logins were logged with each visitor's real address, over IPv4 and IPv6, and a forged X-Forwarded-For header was ignored.
  • Two-factor authentication worked with a time-based code, the kind an authenticator app shows.
  • The interface and the emails were shown in Swedish, both for one user and as the default for new users.
  • With email pointed at a test mail server, over STARTTLS with a login, a password reset, an invite and a notification about a direct message all arrived. Without email, resets and email invites failed, as described in step 8.
  • With the settings from step 3 and Calls off, a capture of the server's traffic showed no outside connections while Mattermost ran and was used, apart from one security check at the first start.
  • An update from Mattermost 11.10.2 to 11.11.1 kept every message and file. Mattermost was restored from a backup into an empty database and empty volumes, and everything came back by itself after a reboot.

The pod used about 230 MB of memory while in use, most of it Mattermost itself.

Before you start

You need:

  • a server set up as in the Podman guide, with Caddy running;
  • an A record and an AAAA record for chat.example.com pointing at your server.

The examples use chat.example.com for Mattermost, you@example.com for your own email address and Example for your team. Replace them throughout.

1. Create the user

As root:

useradd -m -s /bin/bash mattermost
loginctl enable-linger mattermost
machinectl shell mattermost@

Everything up to step 5 runs as mattermost.

2. Create the secrets

pw=$(openssl rand -hex 24)
printf %s "$pw" | podman secret create mattermost-db-password -
printf 'postgres://mattermost:%s@127.0.0.1:5432/mattermost?sslmode=disable&connect_timeout=10' "$pw" | podman secret create mattermost-db-url -
unset pw

Both hold the database password, once for PostgreSQL and once in the address Mattermost connects to. Podman stores them, not the files below. sslmode=disable is fine here: the database is only reached inside the pod.

3. Define the pod

mkdir -p ~/.config/containers/systemd
cd ~/.config/containers/systemd

Create mattermost.pod:

[Pod]
PodName=mattermost
# Only Caddy, on this server, can reach Mattermost: the port is not open to the internet.
PublishPort=127.0.0.1:8065:8065

[Install]
WantedBy=default.target

Create mattermost-db.container:

[Container]
ContainerName=mattermost-db
Image=docker.io/library/postgres:17-alpine
Pod=mattermost.pod
Volume=mattermost-db:/var/lib/postgresql/data
Environment=POSTGRES_USER=mattermost POSTGRES_DB=mattermost
Secret=mattermost-db-password,type=env,target=POSTGRES_PASSWORD
HealthCmd=pg_isready -U mattermost
HealthInterval=10s
Notify=healthy
AutoUpdate=registry

[Service]
Restart=always

And mattermost-app.container:

[Unit]
Requires=mattermost-db.service
After=mattermost-db.service

[Container]
ContainerName=mattermost-app
Image=docker.io/mattermost/mattermost-team-edition:11.11
Pod=mattermost.pod
Volume=mattermost-config:/mattermost/config
Volume=mattermost-data:/mattermost/data
Environment=MM_SERVICESETTINGS_SITEURL=https://chat.example.com
Environment=MM_SERVICESETTINGS_TRUSTEDPROXYIPHEADER=X-Forwarded-For
Environment=MM_SERVICESETTINGS_ENABLEMULTIFACTORAUTHENTICATION=true
Environment=MM_SQLSETTINGS_DRIVERNAME=postgres
Secret=mattermost-db-url,type=env,target=MM_SQLSETTINGS_DATASOURCE
Environment=MM_LOGSETTINGS_ENABLEDIAGNOSTICS=false MM_LOGSETTINGS_ENABLESENTRY=false
Environment=MM_ANNOUNCEMENTSETTINGS_ADMINNOTICESENABLED=false MM_ANNOUNCEMENTSETTINGS_USERNOTICESENABLED=false
Environment=MM_EMAILSETTINGS_PUSHNOTIFICATIONCONTENTS=generic
AutoUpdate=registry

[Service]
Restart=always

What the settings do:

  • SITEURL is the address Mattermost puts in links, invites and emails. It must be the public https:// address.
  • TRUSTEDPROXYIPHEADER makes Mattermost take the visitor's address from the X-Forwarded-For header that Caddy sets. Without it, every login is logged with the server's own address. Caddy replaces any such header a visitor sends, and only Caddy can reach the port, so it cannot be forged.
  • ENABLEMULTIFACTORAUTHENTICATION lets users turn on two-factor authentication (step 6).
  • ENABLEDIAGNOSTICS and ENABLESENTRY stop the usage statistics and error reports Mattermost sends to its makers, and the two NOTICES lines stop it fetching news from notices.mattermost.com every hour.
  • PUSHNOTIFICATIONCONTENTS=generic keeps the text of messages out of notifications to phones, which go through Mattermost's push service (step 9).
  • The 11.11 tag follows every patch release of Mattermost 11.11; step 11 explains how to move to the next release. PostgreSQL stays on 17: a new major version of PostgreSQL needs a dump and a restore.
  • The messages, users and channels are in the database, the uploaded files in the mattermost-data volume, and settings you change in the System Console in config.json in the mattermost-config volume.

Settings given here as MM_ lines win over the System Console, which shows them greyed out. To change one, change the file.

Start it:

systemctl --user daemon-reload
systemctl --user start mattermost-pod
systemctl --user enable --now podman-auto-update.timer

The first start downloads the images, about 1 GB on disk, and Mattermost takes half a minute to set up its database. It is ready when this prints {"status":"OK"} at the end:

curl -s http://127.0.0.1:8065/api/v4/system/ping

4. Create the admin and the team

On a new Mattermost, the first person to sign up becomes its system admin, so create your account before Mattermost can be reached from the internet. Mattermost's command-line tool, mmctl, can do that inside the container:

pw=$(openssl rand -base64 18); echo "$pw"
podman exec mattermost-app mmctl --local user create --email you@example.com --username anna --password "$pw" --system-admin --email-verified
podman exec mattermost-app mmctl --local team create --name example --display-name Example
podman exec mattermost-app mmctl --local team users add example anna
unset pw

The first line makes a random password and prints it: keep it in your password manager. Then the account is created as system admin, a team is created, and you join it. --local talks to Mattermost through a socket inside the container, which the image switches on, so only someone who can run commands as mattermost can use it. You can change the password later under Profile > Security.

Then switch off Calls, Mattermost's voice and video calls:

podman exec mattermost-app mmctl --local plugin disable com.mattermost.calls

Calls needs a UDP port of its own and a STUN server at Mattermost, which this guide does not set up. Without this line, users see call buttons that do not connect.

After the next restart, Calls is gone from mmctl --local plugin list as well: Mattermost unpacks its bundled plugins into the container at every start, and only the ones that are switched on. mmctl --local plugin enable com.mattermost.calls then answers Plugin is not installed. To switch Calls back on later, as mattermost:

echo '{"PluginSettings":{"PluginStates":{"com.mattermost.calls":{"Enable":true}}}}' | podman exec -i mattermost-app mmctl --local config patch /dev/stdin
systemctl --user restart mattermost-app

At the start, Mattermost installs Calls again from its image, and the plugin list shows it as enabled.

5. Put Caddy in front

Go back to root with exit, switch to machinectl shell caddy@, and add this block at the end of ~/Caddyfile:

chat.example.com {
    reverse_proxy 127.0.0.1:8065
}

Restart Caddy with systemctl --user restart caddy, and open https://chat.example.com. Choose View in Browser, and log in with anna and the password from step 4. Caddy passes on the websocket that Mattermost uses for live messages by itself, and has no size limit of its own for uploads.

6. Invite your team

Nobody can sign up without an invite: EnableOpenServer is off by default, and a sign-up without one gets "This server does not allow open signups."

To invite people, open the team menu, Example at the top left, and choose Invite people and Copy invite link. Send the link to your colleagues: it lets them create an account and join the team. It works without email, and anyone who has it can join, so share it only with the people you mean to invite.

A few settings worth knowing, under System Console, in the product menu at the top left:

  • Site Configuration > Users and Teams > Max Users Per Team is 50. Raise it if your team is larger.
  • Environment > File Storage > Maximum File Size is 100 MB. A larger file is refused with "exceeds the maximum allowed 104857600 bytes".
  • User Management > Users lists every account, where you can deactivate those who leave.

Each user can turn on two-factor authentication under Profile > Security > Multi-factor Authentication, with an authenticator app.

7. Use Mattermost in Swedish

Mattermost comes translated into Swedish. Each user picks Svenska under Settings > Display > Language. Swedish users will also want Clock Display set to 24-hour clock in the same place.

To make Swedish the default for new users and for the login page, add this line to mattermost-app.container, as mattermost, and restart:

Environment=MM_LOCALIZATIONSETTINGS_DEFAULTSERVERLOCALE=sv MM_LOCALIZATIONSETTINGS_DEFAULTCLIENTLOCALE=sv
systemctl --user daemon-reload
systemctl --user restart mattermost-app

Accounts made before the change keep their language. Emails go out in each user's language.

8. Email

Mattermost sends email for password resets, invites by email, and notifications about direct messages and mentions to users who are away. This guide does not set up email, and without it:

  • Forgot your password? fails with "Failed to send password reset email successfully.", so you reset passwords for your users yourself (below);
  • invites by email fail with "Email invitations are disabled."; the invite link from step 6 still works;
  • a bar at the top says "Preview Mode: Email notifications have not been configured."

Logins and chat work as usual. To set a new password for a user without email, as mattermost:

podman exec mattermost-app mmctl --local user change-password bo --password 'a-new-password'

To send email, point Mattermost at your email provider's SMTP server. As mattermost, store the SMTP password as a secret:

printf %s 'the-smtp-password' | podman secret create mattermost-mail-password -

Then add these lines to mattermost-app.container, with your own server, account and sender address:

Environment=MM_EMAILSETTINGS_SENDEMAILNOTIFICATIONS=true MM_SERVICESETTINGS_ENABLEEMAILINVITATIONS=true
Environment=MM_EMAILSETTINGS_SMTPSERVER=mail.example.com MM_EMAILSETTINGS_SMTPPORT=587 MM_EMAILSETTINGS_CONNECTIONSECURITY=STARTTLS
Environment=MM_EMAILSETTINGS_ENABLESMTPAUTH=true MM_EMAILSETTINGS_SMTPUSERNAME=chat@example.com
Environment=MM_EMAILSETTINGS_FEEDBACKEMAIL=chat@example.com MM_EMAILSETTINGS_FEEDBACKNAME=Chat
Secret=mattermost-mail-password,type=env,target=MM_EMAILSETTINGS_SMTPPASSWORD

Restart as in step 7, and use System Console > Environment > SMTP > Test Connection to check it. Port 587 with STARTTLS upgrades the connection to TLS; for port 465, which uses TLS from the start, set the port to 465 and CONNECTIONSECURITY to TLS. We tested these settings against a test mail server on the same server, not through a real email provider.

9. Outside services

We captured the server's DNS lookups and outgoing connections from the first start on. On its first start, Mattermost looked up securityupdatecheck.mattermost.com (below), and Calls looked up stun.global.calls.mattermost.com until step 4 switched it off. After that, through restarts, a reboot and everyday use, there were none. Without the lines from step 3, Mattermost also contacted notices.mattermost.com and o94110.ingest.us.sentry.io at each start.

Some things still reach outside when they are used:

  • When someone posts a link, the server fetches the page to show a preview. Site Configuration > Posts > Enable website link previews turns it off.
  • Phone notifications from the Mattermost apps in the App Store and Google Play go through push-test.mattermost.com, a push service Mattermost runs without guarantees for servers without a licence, and on through Apple and Google. With PUSHNOTIFICATIONCONTENTS=generic, they carry the sender and channel name, not the message. We did not test the mobile apps.
  • Mattermost checks securityupdatecheck.mattermost.com for security fixes, according to its documentation, and emails the system admins when one concerns your version. We saw it once, at the first start. Environment > SMTP > Enable Security Alerts turns it off.
  • Marketplace in the product menu fetches the list of plugins from api.integrations.mattermost.com when an admin opens it.

An outgoing webhook or slash command aimed at another app on this same server, such as n8n, needs one of the fixes in step 8 of the Podman guide. According to Mattermost's documentation, it refuses private addresses, such as the one an AddHost line gives, unless they are listed under Environment > Developer > Allow untrusted internal connections to, so the second fix is the simpler one here.

10. Back up

As mattermost:

umask 077
mkdir -p ~/backup
podman exec mattermost-db pg_dump -U mattermost mattermost > ~/backup/mattermost.sql
podman volume export mattermost-data > ~/backup/mattermost-data.tar
podman volume export mattermost-config > ~/backup/mattermost-config.tar

That saves the messages, users and channels, the uploaded files, and the settings from the System Console. The dump runs while Mattermost keeps working. umask 077 makes new files readable by mattermost only; podman volume export --output would ignore it, which is why the lines use >. Copy ~/backup to another machine, for example with restic, and keep it private: it holds every message.

To restore, as mattermost, with the backup in ~/backup:

systemctl --user stop mattermost-app
podman exec mattermost-db dropdb -U mattermost --force mattermost
podman exec mattermost-db createdb -U mattermost mattermost
podman exec -i mattermost-db psql -q -U mattermost mattermost < ~/backup/mattermost.sql
podman volume rm mattermost-data mattermost-config
podman volume create mattermost-data
podman volume create mattermost-config
podman volume import mattermost-data ~/backup/mattermost-data.tar
podman volume import mattermost-config ~/backup/mattermost-config.tar
systemctl --user start mattermost-app

On a new server, run steps 1 to 3 first, then restore. The database password is not in the backup, so new secrets are fine.

11. Update

Once a day, podman-auto-update.timer fetches new images for Mattermost 11.11 and PostgreSQL 17, and restarts the pod with them. To update right away, as mattermost:

podman auto-update
podman image prune -f

Mattermost publishes a new release about every month, with a tag of its own: the version without its last number, such as 11.11 for 11.11.1. To move to a new release, take a backup, change the tag in mattermost-app.container, with NEW replaced by the new tag, and restart:

sed -i 's/team-edition:11.11/team-edition:NEW/' ~/.config/containers/systemd/mattermost-app.container
systemctl --user daemon-reload
systemctl --user restart mattermost-app
podman image prune -a -f

Mattermost updates its database by itself when it starts. Read the release notes before a new major version, such as 12. podman image prune -a removes the old image, about 680 MB; it keeps the images your running containers use. Without -a, the old image stays, as it still has its 11.11 tag. We moved from 11.10 to 11.11 this way, and the team and data were kept.

Troubleshooting

Every login under Profile > Security > View Access History has the server's own address. MM_SERVICESETTINGS_TRUSTEDPROXYIPHEADER is missing from mattermost-app.container. Add it, then daemon-reload and restart mattermost-app.

A setting is greyed out in the System Console. It is set by an MM_ line in mattermost-app.container. Change it there and restart.

The log says "this plugin requires a professional license or higher" for Playbooks. Playbooks comes with Mattermost but needs a paid licence. The message is harmless.

Run it on your own server

SSD RAID 1 · ECC DDR4 · 10 Gbit

VC-P Alloy

€8.99/mo

vCPU
2
Memory
8 GB
Storage
60 GB
Transfer
10 TB
Performance
SSD · RAID 1
  • Full root access
  • Native /64 IPv6
  • RAID-protected storage
  • Malmö, Sweden
  • Month to month, no lock-in
  • 7-day money-back guarantee
All guides