Skip to main content

Mattermost Operations Runbook

Operational reference for the Mattermost Team Edition deployment that powers Equa team messaging.

Infrastructure Overview

ComponentDetails
PlatformRailway (Hobby plan)
Projectequa-mattermost
Project ID56d9cfed-2b45-4452-a6f8-0685ab8f7291
Environmentproduction (751529d4-d056-49c9-b323-6cef763a9f0a)

Services

ServiceImageService IDInternal Domain
Mattermostmattermost/mattermost-team-edition:release-10.4d4b65ddc-6a7c-4a5a-a357-0fb37674f39amattermost.railway.internal
PostgreSQLpostgres:16-alpine6ae96c74-d62c-4957-85f2-2f479768268apostgres.railway.internal

Domains

DomainTypeTarget
mattermost-production.up.railway.appRailway service domainMattermost (port 8065)
chat.equa.ccCustom domain (CNAME)ek6lcxl4.up.railway.app

Volumes

VolumeMount PathService
postgres-volume/var/lib/postgresql/dataPostgreSQL
mattermost-volume/mattermost/dataMattermost

Credentials Inventory

All credentials are stored as Railway environment variables. Never commit credentials to source control.

Mattermost Admin Account

PropertyValue
Usernameequa-admin
Emailshawn@owenent.com
Rolesystem_admin
LocationRailway dashboard or local password manager

Admin Bot Account

PropertyValue
Usernameequa-admin-bot
Display NameEqua Platform
User IDcqh9a9u8wi8i5neqwkfxdntq7a
Token locationMATTERMOST_ADMIN_BOT_TOKEN env var on equa-server Railway project

PostgreSQL

PropertyValue
Databasemattermost
Usermattermost
Password locationPOSTGRES_PASSWORD env var on equa-mattermost Railway project
Host (internal)postgres.railway.internal:5432

Environment Variables on equa-server

VariableValueProject
MATTERMOST_URLhttps://chat.equa.ccequa-server-so
MATTERMOST_ADMIN_BOT_TOKEN(stored in Railway)equa-server-so

DNS Configuration

DNS is managed in Google Cloud DNS, project equa-production, zone equa-cc-zone.
RecordTypeTTLValue
chat.equa.ccCNAME300ek6lcxl4.up.railway.app
_railway-verify.chat.equa.ccTXT300railway-verify=2463d41c43c8bfab337b245fbcac8b9264790df52a78c45a2a715fa003963a98

Modifying DNS

# Requires gcloud auth with access to equa-production project
gcloud auth login
gcloud dns record-sets update chat.equa.cc. \
  --zone=equa-cc-zone \
  --type=CNAME \
  --ttl=300 \
  --rrdatas="NEW_TARGET." \
  --project=equa-production

Monitoring

Health Check

curl https://chat.equa.cc/api/v4/system/ping
# Expected: {"status":"OK", ...}

Admin Token Verification

curl -H "Authorization: Bearer $MATTERMOST_ADMIN_BOT_TOKEN" \
  https://chat.equa.cc/api/v4/users/me
# Expected: {"username":"equa-admin","roles":"system_admin system_user", ...}
Despite the env var name MATTERMOST_ADMIN_BOT_TOKEN, this is a personal access token belonging to the equa-admin regular user, not the bot account.

Check Teams

curl -H "Authorization: Bearer $MATTERMOST_ADMIN_BOT_TOKEN" \
  https://chat.equa.cc/api/v4/teams

Common Operations

Restart Mattermost

Via Railway CLI:
railway link --project equa-mattermost
railway service mattermost
railway redeploy
Via Railway GraphQL API:
curl -X POST https://backboard.railway.com/graphql/v2 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $RAILWAY_TOKEN" \
  -d '{"query":"mutation { serviceInstanceRedeploy(environmentId: \"751529d4-d056-49c9-b323-6cef763a9f0a\", serviceId: \"d4b65ddc-6a7c-4a5a-a357-0fb37674f39a\") }"}'

View Deployment Logs

railway link --project equa-mattermost
railway service mattermost
railway logs

Update Mattermost Version

Update the Docker image tag in Railway:
# Via GraphQL API - update the service source image
curl -X POST https://backboard.railway.com/graphql/v2 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $RAILWAY_TOKEN" \
  -d '{"query":"mutation { serviceUpdate(id: \"d4b65ddc-6a7c-4a5a-a357-0fb37674f39a\", input: { source: { image: \"mattermost/mattermost-team-edition:release-10.5\" } }) { id } }"}'
Then redeploy the service.

Rotate Admin Token

MATTERMOST_ADMIN_BOT_TOKEN is a personal access token belonging to the equa-admin regular user, not the bot account. Bot tokens cannot create PATs for other users due to a Mattermost platform limitation.
Standard rotation (zero downtime):
  1. Log into Mattermost as equa-admin (shawn@owenent.com)
  2. Go to Profile > Security > Personal Access Tokens
  3. Click Create Token, give it a description (e.g., Equa session 2026-03)
  4. Copy the new token value
  5. In Railway (equa-server-so project), update MATTERMOST_ADMIN_BOT_TOKEN to the new token
  6. Wait for equa-server to redeploy
  7. Verify: POST /api/v1/mattermost/session succeeds
  8. Revoke the old token in Mattermost (Profile > Security > Personal Access Tokens)
The old token continues working until explicitly revoked, so there is no downtime window between steps 5 and 8. Emergency recovery (admin locked out):
  1. Enable the TCP proxy (see TCP Proxy Management below)
  2. Connect to the Mattermost database:
psql -h shinkansen.proxy.rlwy.net -p 27531 -U mattermost -d mattermost
  1. Reset the admin password:
-- Generate hash with: node -e "console.log(require('bcryptjs').hashSync('NewPassword123!', 10))"
UPDATE users SET password = '$2a$10$...' WHERE username = 'equa-admin';
  1. Log in with the new password, create a new PAT
  2. Update MATTERMOST_ADMIN_BOT_TOKEN in Railway
  3. Disable the TCP proxy

Full Member Sync

If member state is out of sync between Equa and Mattermost:
# Via equa-server API (requires admin session cookie)
curl -X POST https://equa-server-so-production.up.railway.app/api/v1/mattermost/sync-org/{orgId} \
  -H "Cookie: session=..."

Mattermost Environment Variables

Key configuration on the Mattermost Railway service:
VariableValuePurpose
MM_SQLSETTINGS_DRIVERNAMEpostgresDatabase driver
MM_SQLSETTINGS_DATASOURCEpostgres://mattermost:***@postgres.railway.internal:5432/mattermost?sslmode=disableDatabase connection
MM_SERVICESETTINGS_SITEURLhttps://chat.equa.ccPublic site URL
MM_SERVICESETTINGS_LISTENADDRESS:8065Listen port
MM_SERVICESETTINGS_ENABLEPERSONALACCESSTOKENStrueRequired for API-provisioned auth
MM_SERVICESETTINGS_ENABLEBOTACCOUNTCREATIONtrueRequired for admin bot
MM_SERVICESETTINGS_ENABLEUSERACCESSTOKENStrueRequired for user provisioning
MM_SERVICESETTINGS_ALLOWCORSFROMhttps://app.equa.ccCORS for cross-origin API requests (does not control iframe embedding — see Nginx Reverse Proxy section)
MM_TEAMSETTINGS_ENABLEOPENSERVERfalsePrevent public registration
MM_EMAILSETTINGS_ENABLESIGNUPWITHEMAILfalsePrevent email signup (users provisioned via Equa only)
PORT8065Railway port binding

Nginx Reverse Proxy

Mattermost returns X-Frame-Options: SAMEORIGIN by default, which blocks cross-origin iframe embedding. Since equa-web embeds Mattermost in an iframe, an nginx reverse proxy sits in front of the Mattermost service to strip this header and add permissive Content-Security-Policy: frame-ancestors rules.

Architecture

equa-web (iframe src=chat.equa.cc)
  → chat.equa.cc (DNS CNAME)
    → Nginx Reverse Proxy (Railway service, port 8080)
      → Mattermost (mattermost.railway.internal:8065)

Configuration

Source: equa-server/infra/mattermost-proxy/
FilePurpose
Dockerfilenginx:alpine with template-based config
nginx.confStrips X-Frame-Options, adds frame-ancestors, proxies to Mattermost
README.mdDeployment instructions

Environment Variables

VariableDescriptionExample
MATTERMOST_UPSTREAMInternal Mattermost URLhttp://mattermost.railway.internal:8065

Deployment

  1. In the equa-mattermost Railway project, create a new service from equa-server/infra/mattermost-proxy/
  2. Set MATTERMOST_UPSTREAM to the internal Mattermost URL
  3. Move the chat.equa.cc custom domain from the Mattermost service to the proxy service
  4. The proxy listens on port 8080 (auto-detected from the Dockerfile EXPOSE)

Verification

# Confirm X-Frame-Options is stripped
curl -I https://chat.equa.cc | grep -i x-frame-options
# Should return nothing

# Confirm frame-ancestors is set
curl -I https://chat.equa.cc | grep -i content-security-policy
# Should return: frame-ancestors 'self' https://equa-web-so-production.up.railway.app https://app.equa.cc
If the proxy is bypassed or removed, the messaging iframe will show a blank page. The X-Frame-Options header is set by Mattermost itself and cannot be disabled via Mattermost configuration.

TCP Proxy Management

The Mattermost PostgreSQL database has a TCP proxy for emergency direct access.
PropertyValue
Hostshinkansen.proxy.rlwy.net
Port27531
Usermattermost
Databasemattermost
SSLDisabled

When to Enable

  • Password resets for locked-out admin accounts
  • Direct role modifications not possible via the API
  • Database migration troubleshooting
  • One-time data corrections

When to Disable

Disable the TCP proxy whenever it is not actively needed for admin operations.

How to Toggle

  1. Open the equa-mattermost Railway project
  2. Navigate to the PostgreSQL service
  3. Go to Settings > Networking > Public Networking
  4. Toggle the TCP proxy off (disable) or on (enable)

Risk Assessment

StateRiskMitigation
EnabledDatabase reachable from public internetProtected by database credentials; but credentials could be leaked via env vars, logs, or config drift
DisabledNo remote DB access for emergency opsUse Railway built-in query interface or re-enable temporarily
Recommendation: Keep disabled by default. Enable only for specific admin tasks, then disable immediately after.

Scaling Considerations

Mattermost Team Edition on Railway Hobby plan:
ResourceCurrentLimitAction if exceeded
RAM~512MBHobby plan limitsUpgrade Railway plan or migrate to dedicated VPS
StorageVolume-backedHobby plan limitsMonitor via Railway dashboard
ConnectionsLow (single org testing)PostgreSQL connection limitsAdd connection pooling if needed
For production scale (50+ concurrent users), consider:
  • Upgrading to Railway Pro plan
  • Adding Redis for session caching (MM_CACHEETTINGS_CACHETYPE=redis)
  • Configuring S3 for file storage (MM_FILESETTINGS_DRIVERNAME=amazons3)
  • Setting up log aggregation