Learn how to deploy a self-hosted Mattermost Team Edition server on Upsun, with PostgreSQL 16 and OpenSearch 2 configured automatically through a single infrastructure-as-code file.
Mattermost Team Edition is the open-source, self-hosted version of Mattermost’s team messaging platform. If you want full control over your team’s communication data without relying on a third-party SaaS service, it’s a solid choice. On Upsun, the infrastructure is defined entirely in a single YAML file, and the deployment lifecycle is automated through build, deploy, and post-deploy hooks.This article walks through the template that deploys Mattermost Team Edition 11.4.0 on Upsun, backed by PostgreSQL 16 for storage and OpenSearch 2 for full-text message search.
The template used in this article is a community-maintained deployment template, not an official Upsun-supported project.
The repository already contains all the configuration you need. No application code is written by you — the build hook downloads the Mattermost binary directly.
Seven persistent mounts keep Mattermost’s config, logs, data, and plugin directories intact across deployments. These use instance storage, so they’re local to each container rather than shared.
Routes send all traffic to www.{default} and redirect the bare domain to it. Caching is disabled because Mattermost serves dynamic content.
The start command runs the Mattermost binary directly — there’s no additional web server in front of it.
The build.sh script runs during the build phase, before the container is deployed:
#!/usr/bin/env bashexport MATTERMOST_VERSION=$(cat mattermost_version)download_mattermost() { local tarball="mattermost-team-${MATTERMOST_VERSION}-linux-amd64.tar.gz" local cache_file="${PLATFORM_CACHE_DIR}/${tarball}" if [ -f "$cache_file" ]; then printf "\n ✔ \033[1mUsing cached Mattermost archive\033[0m ($MATTERMOST_VERSION)\n\n" else printf "\n ✔ \033[1mDownloading Mattermost...\033[0m ($MATTERMOST_VERSION)\n\n" wget --quiet -c "https://releases.mattermost.com/${MATTERMOST_VERSION}/${tarball}" -O "$cache_file" fi tar -xzf "$cache_file" cp -a mattermost/* . chmod +x bin/mattermost}set_config() { cp config/config.json config.default}set -edownload_mattermostset_config
The version is read from the mattermost_version file (currently 11.4.0). The tarball is cached in PLATFORM_CACHE_DIR so subsequent builds skip the download if the version hasn’t changed. After extraction, a copy of the default config.json is saved as config.default for the deploy hook to restore if needed.
deploy.sh runs after each build before the app starts:
#!/usr/bin/env bashset_config() { if [ ! -f config/config.json ] || [ ! -s config/config.json ]; then cp config.default config/config.json fi}set_local(){ mkdir -p .config/local/ if [ -e ".config/local/mattermost_local.socket" ] && [ ! -S ".config/local/mattermost_local.socket" ]; then rm -f .config/local/mattermost_local.socket fi}set_configset_local
It restores config.json from the backup if the file is missing or empty, and ensures the local socket directory exists. Any stale non-socket file at the socket path is removed to prevent Mattermost from failing to bind on startup.
The .environment file is sourced on every container start and runtime event. It translates Upsun’s platform variables into Mattermost’s MM_* configuration format:
# Basics.export MM_SERVICESETTINGS_LISTENADDRESS=":$PORT"export MM_SERVICESETTINGS_SITEURL=$(echo "$PLATFORM_ROUTES" | base64 --decode | jq -r 'to_entries[] | select(.value.primary) | .key')export MM_SERVICESETTINGS_ALLOWCORSFROM=$(echo "$PLATFORM_ROUTES" | base64 --decode | jq -r 'to_entries[] | select(.value.primary) | .key')# Customization.export MM_TEAMSETTINGS_ENABLECUSTOMBRAND=true# PostgreSQL.export DATABASE_HOST=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".database[0].host")export DATABASE_PORT=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".database[0].port")export DATABASE_NAME=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".database[0].path")export DATABASE_USER=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".database[0].username")export DATABASE_PASSWORD=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".database[0].password")export MM_SQLSETTINGS_DRIVERNAME="postgres"export MM_SQLSETTINGS_DATASOURCE="postgres://$DATABASE_USER:$DATABASE_PASSWORD@$DATABASE_HOST:$DATABASE_PORT/$DATABASE_NAME?sslmode=disable&connect_timeout=10"# Elasticsearch.export ELASTIC_HOST=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".essearch[0].host")export ELASTIC_PORT=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".essearch[0].port")export MM_ELASTICSEARCHSETTINGS_CONNECTIONURL="http://$ELASTIC_HOST:$ELASTIC_PORT"export MM_ELASTICSEARCHSETTINGS_USERNAME=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".essearch[0].username")export MM_ELASTICSEARCHSETTINGS_PASSWORD=$(echo $PLATFORM_RELATIONSHIPS | base64 --decode | jq -r ".essearch[0].password")# Logging.export MM_LOGSETTINGS_ENABLEFILE=trueexport MM_LOGSETTINGS_FILELOCATION="./logs/"# Notifications.export MM_NOTIFICATIONLOGSETTINGS_ENABLEFILE=falseexport MM_NOTIFICATIONLOGSETTINGS_FILELOCATION="./logs/"# Plugins.export MM_PLUGINSETTINGS_ENABLEUPLOADS=true# Email.export MM_EMAILSETTINGS_SMTPSERVER=$PLATFORM_SMTP_HOSTexport MM_EMAILSETTINGS_SMTPPORT=25# Local mode, for setting up the first user on first deploy.export MM_SERVICESETTINGS_ENABLELOCALMODE=trueexport MM_SERVICESETTINGS_LOCALMODESOCKETLOCATION="/app/.config/local/mattermost_local.socket"export MMCTL_LOCAL_SOCKET_PATH="/app/.config/local/mattermost_local.socket"# First deploy details.export PSH_INITADMIN_USERNAME=adminexport PSH_INITADMIN_EMAIL=admin@example.comexport PSH_FIRSTTEAM_NAME=team-adminexport PSH_FIRSTTEAM_DISPLAYNAME=team-adminexport PSH_FIRSTCHANNEL_NAME=setupexport PSH_FIRSTCHANNEL_DISPLAYNAME=Setupexport PSH_WELCOME_MESSAGE="Congrats @admin! You have successfully deployed your own Mattermost server on Upsun!"export PSH_WARNING_MESSAGE1="**WARNING:** A randomly generated password was created for this System Admin account. Retrieve it by running: \`cat /app/.config/admin_credentials\` via SSH"export PSH_WARNING_MESSAGE2="**WARNING:** Once logged in, go to **Account Settings** in the top right of the toolbar and update your credentials immediately."
PLATFORM_RELATIONSHIPS and PLATFORM_ROUTES are base64-encoded JSON blobs that Upsun injects at runtime. The .environment file decodes them with jq to extract service hostnames, ports, and credentials without you needing to manage secrets manually. The PSH_* variables at the bottom define the default admin account, first team, first channel, and the welcome messages posted on first deploy.
Create a new Upsun project from the repository root:
upsun project:create
Follow the CLI prompts to name your project and select a region. Then push to deploy:
upsun push
The first deployment takes a few minutes. The build hook downloads the Mattermost binary (~200 MB), and the post-deploy hook initializes the admin account after the app starts.
The build hook will download the new tarball (or use a cached copy if it already downloaded it), and Upsun’s zero-downtime deployment will replace the running instance.
The plugins and client/plugins mounts persist across deployments, so any plugins you install through the Mattermost marketplace or by uploading a plugin bundle survive redeployments. Plugin uploads are enabled by default via MM_PLUGINSETTINGS_ENABLEUPLOADS=true in .environment.
This template gives you a fully functional Mattermost Team Edition instance on Upsun with no manual infrastructure setup. PostgreSQL and OpenSearch are provisioned automatically, the admin account is created on first deploy, and all configuration is driven by environment variables — so there are no secrets in your repository.Get started with a free Upsun account at upsun.com.
Last modified on April 27, 2026
Was this page helpful?
Assistant
Responses are generated using AI and may contain mistakes.