Backups and upgrades
What to back up, how to restore it on a new host, how to upgrade, and how to move a Mattermost server or a single community to Antimatter.
What to back up
| What | With the site's compose file | With a release archive | With the .deb or .rpm package |
|---|---|---|---|
| The database: messages, users, communities, channels and, with the site's compose file, the configuration | volume antimatter_postgres, through pg_dump | your PostgreSQL database | your PostgreSQL database |
| Uploaded files, images, and installed apps | volume antimatter_files (the S3 storage) | FileSettings.Directory, ./data/ by default | /var/lib/antimatter |
| The configuration file | (in the database) | config/config.json | /etc/antimatter/config/config.json |
| GIFs and stickers | volume antimatter_gifs | the GIF service's data directory | /var/lib/gifs-server |
| Secrets | .env (and compose.override.yaml) | your environment file | /etc/antimatter/antimatter.env, /etc/gifs-server |
| The AI encryption key, which encrypts the keys of model connections | AM_AI_ENCRYPTION_KEY in .env (recommended), or the file ai-encryption.key in the volume antimatter_data | AM_AI_ENCRYPTION_KEY, or data/ai-encryption.key | AM_AI_ENCRYPTION_KEY in antimatter.env, or /var/lib/antimatter/data/ai-encryption.key |
Without the AI encryption key, a restored server can't read the API keys of its model connections, and each one has to be typed again. Keep a copy of the key apart from the database backups, so that one stolen backup doesn't hold both. See The encryption key.
The database and the files belong together: back them up at the same time, and keep copies off the server. Volume names start with the compose project name, antimatter (docker volume ls lists them).
Back up
From the directory of compose.yaml. Stopping Antimatter first gives a fully consistent copy; a backup taken while it runs is usually fine, but files uploaded during the backup may be missing from it.
stamp=$(date +%Y%m%d-%H%M%S)
mkdir -p backups
docker compose stop antimatter
# Database
docker compose exec -T postgres pg_dump -U antimatter -d antimatter -Fc \
> "backups/antimatter-db-$stamp.dump"
# File storage, GIF service and the data directory (the AI key file)
for v in files gifs data; do
docker run --rm -v "antimatter_$v:/data:ro" -v "$PWD/backups:/backup" \
alpine tar -czf "/backup/antimatter-$v-$stamp.tar.gz" -C /data .
done
docker compose start antimatter
# Secrets
cp .env "backups/env-$stamp"
If you haven't set AM_AI_ENCRYPTION_KEY, the server's key file is in the data volume, which the loop above saves with the files. Better, put its content in .env as The encryption key explains, and keep a copy of it apart from the database backups.
Run it from cron for daily backups, and copy the backups/ directory to another machine. Keep the copies of .env as safe as the server: they hold its passwords.
To copy the uploaded files object by object instead (for example to another S3 storage), use the AWS command-line tool of the bucket service:
docker compose run --rm -v "$PWD/backups/files:/backup" bucket \
"aws --endpoint-url http://files:9000 s3 sync s3://antimatter /backup"
With a release archive, back up the database with pg_dump -Fc, and the data/ and config/ directories with your usual tools (data/ holds the AI key file). With the packages, back up the database the same way, and /etc/antimatter, /var/lib/antimatter, /etc/gifs-server and /var/lib/gifs-server.
Restore on a new host
Put compose.yaml, the saved .env and the backups in a new directory, then:
stamp=20261016-030000 # the backup to restore
docker compose create # creates the volumes, starts nothing
docker compose up -d postgres
docker compose exec -T postgres pg_restore -U antimatter -d antimatter --no-owner \
< "backups/antimatter-db-$stamp.dump"
for v in files gifs data; do
docker run --rm -v "antimatter_$v:/data" -v "$PWD/backups:/backup" \
alpine tar -xzf "/backup/antimatter-$v-$stamp.tar.gz" -C /data
done
docker compose up -d
Use the same .env: the storage and the database must keep the passwords they had, and the model connections need the same AI encryption key. If the new host has another address, update SITE_URL (and DOMAIN).
Test your backups by restoring them on another machine from time to time.
Upgrade
data volume: the AI key file was in the container. Before you use the new compose.yaml, and if .env has no AM_AI_ENCRYPTION_KEY, save the key the server made: echo "AM_AI_ENCRYPTION_KEY=$(docker compose exec -T antimatter cat /antimatter/data/ai-encryption.key)" >> .env. If the command finds no file, no model connection has saved a key yet.- Take a backup.
- Change the image tag in
.env(ANTIMATTER_IMAGE), if you pin one, as recommended. - Pull and restart:
docker compose pull docker compose up -d docker compose logs -f antimatter
On start, the server upgrades the database schema (follow it in the log; a large server can take several minutes) and installs the newer versions of the bundled apps from the image. With a release archive: stop the server, replace everything except config/, data/, logs/, plugins/ and client/plugins/ with the new archive's content, and start it again. With the .deb or .rpm package, install the new package as Upgrade a package explains.
antimatter-next back to antimatter.Versions
Antimatter's server reports the version of the Mattermost line it is based on (currently 12.0.0). The releases on GitHub, with their archives and packages, have their own version numbers, in the 0.9.0 series during the beta: antimatter version shows it as Build Number. The images are rebuilt from the main branches: antimatter-next every night (tags nightly-<YYYYMMDD>), and both images on each change (tags sha-<commit>). Pin one of these tags, upgrade on purpose, and keep the previous tag noted so you know which backup goes with it.
PostgreSQL major versions
The compose file uses postgres:17-alpine. Antimatter needs PostgreSQL 15 or newer. A new major version of PostgreSQL can't read the previous one's data directory: to move to one, dump the database, start the new version on a new volume and restore the dump into it.
Upgrading to 0.9.0
The first start of version 0.9.0 (or of an image built from 17 October 2026 on) makes a few one-time changes to the data. Each runs once, and is recorded in the database. They bring what was left at an old default to the new one, and leave alone what an administrator chose. Follow them in the log.
AM_TEAMSETTINGS_DEFAULTCOMMUNITYKIND=workplace. The upgrade gives every community the kind this setting has at that moment, and calls are off by default in the channels of communities of the Guild kind. See Guild or workspace.| What | The change |
|---|---|
| Kinds of community | Every community gets a kind, Guild or Workspace: the value of Kind of new communities (TeamSettings.DefaultCommunityKind) at the first start. A kind already set is kept. Community admins can change their community's kind afterwards, in Community settings › General. |
| Who archives channels | Channel members no longer archive the channels they are in: community admins do. In a workspace, the channel's admins (whoever created it, and those they made admins) also archive it. This changes the roles of the system scheme; the permission schemes a system administrator made for some communities are left as they are. |
| The Moderators role of the layout presets | The Moderators role that communities got from a layout preset gets Remove people, which timing people out and removing them need. Only roles still exactly as the preset made them change; a role someone edited is left alone. |
| Roles with Administrator | A community role with the Administrator switch on now holds everything the community's admins can do, as the switch says, not only the community's settings. |
| The member limit | Max Members Per Community (TeamSettings.MaxUsersPerTeam) goes from 50, the old default, to 0 (no limit). Any other value is kept, and so is a limit set with AM_TEAMSETTINGS_MAXUSERSPERTEAM. |
| Model connections | System administrators get the management of model connections, and community admins (and roles with Administrator) that of their community's own. The AI services of the Agents app become server-wide model connections, see below. |
| Town Square becomes General | Every community's default channel is renamed from town-square (Town Square) to general (General). It keeps its ID, members, messages and settings, and a display name the admins chose. Old links and lookups by town-square still lead to it. A community that already has a channel named general, even archived, keeps its town-square as its default channel, and so does a community whose town-square is archived: the log names them, and their admins may rename the channels. |
| Favourites | Everyone's Favorites sidebar category is renamed Favourites, as the interface now says. Categories people created with that name are left alone. |
The Agents app's services
The upgrade copies each AI service of the Agents app into a server-wide model connection, shared with every community and usable by everyone but guests (turn on Guests too to let them). The keys are encrypted on the way, so the server needs its AI encryption key: on a cluster whose files are in S3, set AM_AI_ENCRYPTION_KEY first, or the copy waits for the next start. Then check System Console › AI › Connections and agents:
- Each connection keeps the ID of its service, so the app's agents and an auto-translation set to an Agents service still find it.
- OpenAI, OpenAI-compatible, Anthropic, Mistral, Cohere and Gemini services become connections to their OpenAI-compatible (or Anthropic) endpoint. An Azure service becomes a connection to the resource's OpenAI-compatible endpoint: test it.
- AWS Bedrock, Google Vertex AI and Cohere North services, and unknown types, are marked Needs attention, with the reason. Their cloud credentials aren't copied: point them at the provider's OpenAI-compatible endpoint or a gateway such as LiteLLM, then save.
- The embedding service becomes the Embeddings model of the connection with the same address and key, or a connection of its own. The transcription service gets the
whisper-1model. - When the rewrite and recaps have no connection yet, they get the service and model of the app's default agent.
The Agents app's agents
After the services, the upgrade turns each agent of the Agents app (now the AI chat app) into a server-wide agent, on the same account: its connection (the one copied from its service), model, instructions, reasoning, tool turns and who may use it. The server then answers for these agents. Where the app limited an agent in a way the server's agents don't express (the channels where it answered, block lists, attribute-based access), the agent is narrowed to the members of those channels, or to nobody, and marked as needing attention, as is an agent whose service didn't become a connection. Check them in System Console › AI › Connections and agents. The app's other settings are left as they were.
Other changes of defaults
- Calls: Test mode is off and Enable video is on by default. A choice saved in the configuration is kept. Calls also follow the kind of community (see Calls and voice).
- Putting messages on top is for the channel's admins, the community's admins and moderators (with Manage messages); members of a direct or group message still can.
- Archived channels can take new members, who read their history. Nobody posts, reacts or changes anything in them.
Search index
The new search filters (has:, pinned: and mentions:; see Filters) need fields that didn't exist in the old index. If your server searches with Elasticsearch or OpenSearch (System Console › Environment › Elasticsearch), rebuild the index after upgrading: Purge Indexes, then Index Now. Until you do, those filters quietly return no results through the search engine, while the database fallback still has them. A server that searches the database only needs nothing.
Moving from Mattermost
Antimatter uses Mattermost's database schema and adds its own tables, so it can take over a Mattermost server's PostgreSQL database and files. Try it on a copy first:
- Back up the Mattermost database, its file storage (
data/or S3 bucket) andconfig.json, then stop Mattermost. - Restore the database into Antimatter's PostgreSQL, or point Antimatter at a copy of it (
AM_SQLSETTINGS_DATASOURCE). - Copy the files into Antimatter's storage, keeping their paths: to the
antimatterbucket with an S3 tool, or todata/with a release archive. - Reuse your settings: copy
config.json, or move it into the database withamctl --local config migrate(see Configuration). YourMM_environment variables keep working; rename them toAM_when convenient. - Start Antimatter and follow the log while it upgrades the database. Then check System Console › Plugins › Plugin Management. Antimatter installs its own builds of Calls, AI chat and the integrations when they are newer than the installed version, and of Playbooks and Kanban (Boards) too if the Mattermost server already had either installed (neither is bundled on a new server any more: see Bundled apps). If Mattermost had a newer version of one of them, remove that app and restart Antimatter, so that its own build is installed.
- Set up push notifications again, with your own push proxy: Antimatter never sends notifications to Mattermost's push services, test or hosted, which only deliver to Mattermost's own apps. Until you change Push Notification Server, the server sends no push notifications (see Push notifications).
Antimatter contacts no service run by Mattermost, Inc. (see Connections to outside services), even when your configuration still names one:
- a push notification server at Mattermost is kept in the settings but never used;
- Mattermost's addresses for the app marketplace (Marketplace URL) and for product notices are emptied;
- the app download links that were Mattermost's defaults (Antimatter Apps Download Page Link, Android App Download Link and iOS App Download Link) are replaced by the download pages of Antimatter's apps on GitHub; links you set yourself are kept;
- the Calls app uses Cloudflare's public STUN server instead of Mattermost's, without changing the saved setting (see Calls);
- the security update check and crash reporting are gone, and so are their System Console settings (Enable Security Alerts, Enable Diagnostics and Error Reporting) and the Notices page.
The local mode socket is now /var/tmp/antimatter_local.socket; amctl also tries the old /var/tmp/mattermost_local.socket. Mattermost only supports PostgreSQL in its recent versions, and so does Antimatter: move a MySQL database to PostgreSQL first. Going back to Mattermost after Antimatter has upgraded the database means restoring your Mattermost backup.
Export and import data
The bulk export format moves data between servers: the whole server, some communities, or some channels, with attachments, profile pictures and custom emoji if you want them.
# on the source server
amctl export create --team-name design --include-profile-pictures
amctl export list
amctl export download <export file> design.zip
# on the destination server
amctl import upload design.zip
amctl import list available
amctl import process <uploaded file> --imported-users active
Watch the jobs with amctl export job list and amctl import job list. For a community or channel export, --imported-users is required: active when the people move to this server, inactive when importing into a server where they already have accounts, so accounts created from uncertain matches stay off until an administrator checks them. --destination-team-name imports into an existing community. Exports and imports are kept on the server's storage (ExportSettings.Directory, ImportSettings.Directory) for 30 days by default.
On the server itself, antimatter export bulk and antimatter import bulk --apply do the same from files, without the API.
A channel's line in the export carries its type and settings (am_type, am_mode, am_root_posts and, for forums, am_forum_view); a line without them imports as a plain text Chat channel. A root post's line into a forum channel needs props.am_thread_title, its topic's title; without one, importing fails on that line.