Administrators Guide¶
This guide is for technical volunteers responsible for deploying and maintaining the Equipment Status Board. It covers Docker deployment, environment configuration, Slack App setup, and ongoing maintenance.
Prerequisites¶
Before you begin, ensure you have:
- Docker and Docker Compose installed on the server
- Git for cloning the repository
- A server or machine on the makerspace local network (or accessible to members)
- A Slack workspace with a paid plan (Pro or higher) if you want to use Slack integration
Installation & Deployment¶
1. Clone the Repository¶
git clone https://github.com/jantman/equipment-status-board.git
cd equipment-status-board
2. Configure Environment Variables¶
cp .env.example .env
Edit .env and set the required values. See the Environment Variable Reference below for details on each variable.
At minimum, you must change:
SECRET_KEY— Set to a random string for production (e.g.,python3 -c "import secrets; print(secrets.token_hex(32))")MARIADB_ROOT_PASSWORD— Set a strong database password
3. Start All Services¶
docker compose up -d
This starts three containers: the web application, the MariaDB database, and the background notification worker.
4. Run Database Migrations¶
docker compose exec app flask db upgrade
This creates all required database tables.
5. Create the First Staff User¶
docker compose exec app flask seed-admin <username> <email> --password <password>
For example:
docker compose exec app flask seed-admin admin admin@example.com --password changeme123
This creates a user with the Staff role who can then log in and create additional users through the web interface.
6. Verify¶
Open http://localhost:5000 in a browser (or the server's IP/hostname on port 5000). You should see the status dashboard. Log in with the Staff user you just created.
Environment Variable Reference¶
| Variable | Description | Required | Default | Example |
|---|---|---|---|---|
SECRET_KEY |
Flask secret key for session signing. Must be random in production. | Yes | dev-secret-change-me |
a1b2c3d4e5f6... (use python3 -c "import secrets; print(secrets.token_hex(32))") |
DATABASE_URL |
SQLAlchemy database connection URL. In Docker, the hostname is db. |
Yes | mysql+pymysql://root:esb_dev_password@localhost/esb |
mysql+pymysql://root:yourpassword@db/esb |
MARIADB_ROOT_PASSWORD |
Root password for the MariaDB container. Must match the password in DATABASE_URL. |
Yes | esb_dev_password |
strong-random-password |
UPLOAD_PATH |
Directory for uploaded files (photos, documents). Relative to app root or absolute path. | No | uploads |
/app/uploads |
UPLOAD_MAX_SIZE_MB |
Maximum upload file size in megabytes. | No | 500 |
100 |
SLACK_BOT_TOKEN |
Slack Bot User OAuth Token. Leave empty to disable Slack integration. | No | (empty) | xoxb-1234567890-... |
SLACK_SIGNING_SECRET |
Slack Signing Secret for verifying requests from Slack. | No | (empty) | abc123def456... |
SLACK_OOPS_CHANNEL |
Slack channel for cross-area notifications. Can be set in .env (not included in .env.example by default). |
No | #oops |
#equipment-alerts |
STATIC_PAGE_PUSH_METHOD |
How to publish the static status page. Options: local (write to directory), s3 (upload to S3 bucket via boto3), or gcs (upload to Google Cloud Storage bucket). |
No | local |
s3 |
STATIC_PAGE_PUSH_TARGET |
Target for static page push. For local: a directory path. For s3 and gcs: bucket-name/optional/key/path (key defaults to index.html). |
No | (empty) | my-status-bucket/index.html |
FLASK_APP |
Flask application entry point. Do not change. | No | esb:create_app |
esb:create_app |
FLASK_DEBUG |
Enable Flask debug mode. Set to 0 in production. |
No | 1 |
0 |
AWS_ACCESS_KEY_ID |
AWS access key for S3 static page push. Only needed if STATIC_PAGE_PUSH_METHOD=s3 and not using an IAM role. |
No | (empty) | AKIAIOSFODNN7EXAMPLE |
AWS_SECRET_ACCESS_KEY |
AWS secret key for S3 static page push. Only needed if STATIC_PAGE_PUSH_METHOD=s3 and not using an IAM role. |
No | (empty) | wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY |
GOOGLE_APPLICATION_CREDENTIALS |
Path to Google Cloud service account JSON key file. Only needed if STATIC_PAGE_PUSH_METHOD=gcs and not using instance metadata or Workload Identity. |
No | (empty) | /path/to/service-account.json |
Warning
Always set SECRET_KEY to a unique random value in production. The default value is insecure and only suitable for development.
Warning
Set FLASK_DEBUG=0 in production. Debug mode exposes detailed error pages and enables the interactive debugger.
Docker Services¶
The application runs as three Docker containers defined in docker-compose.yml:
App Service¶
The main web application. Runs Flask via Gunicorn with 2 worker processes on port 5000.
- Image: Built from the project
Dockerfile(Python 3.14-slim base) - Port: 5000 (mapped to host)
- Volume:
./uploadsbind mount for persistent file storage (uploaded photos and documents) - Depends on:
dbservice (waits for healthy database)
Database Service¶
MariaDB 12.2.2 database server. Stores all application data.
- Image:
mariadb:12.2.2 - Volume:
mariadb_datanamed volume for persistent data storage - Health check: Pings the database every 10 seconds to verify availability
- Port: Not mapped to host (only accessible from other containers)
Worker Service¶
Background notification processor. Polls the database every 30 seconds for pending notifications and delivers them via Slack.
- Image: Same as the app service
- Command:
flask worker run - Depends on:
dbservice
All three services have a restart policy of unless-stopped, meaning they automatically restart after crashes or host reboots (unless explicitly stopped).
Runtime Dependencies¶
The application Docker image includes these key Python packages:
- Flask — Web framework
- SQLAlchemy / Flask-SQLAlchemy — Database ORM
- PyMySQL — MariaDB database driver
- slack-bolt / slack_sdk — Slack integration (slash commands, modals, events)
- boto3 — AWS S3 client for static page push (when using
s3method) - google-cloud-storage — Google Cloud Storage client for static page push (when using
gcsmethod) - qrcode[pil] — QR code generation for equipment pages
- gunicorn — Production WSGI server
Slack App Configuration¶
Slack integration is optional — the core web application works without it. If you want Slack commands, notifications, and the status bot, follow these steps.
1. Create a Slack App¶
- Go to api.slack.com/apps and click Create New App
- Choose From scratch
- Name the app (e.g., "Equipment Status Board") and select your workspace
2. Configure Bot Token Scopes¶
Under OAuth & Permissions, add these Bot Token OAuth Scopes:
chat:write— Send messages and notificationscommands— Register slash commandsusers:read— Look up user informationusers:read.email— Look up users by emailim:write— Send direct messages (for temporary password delivery)
3. Set Up Slash Commands¶
Under Slash Commands, create four commands. All commands use the same Request URL:
https://<your-domain>/slack/events
| Command | Description |
|---|---|
/esb-report |
Report an equipment problem |
/esb-status |
Check equipment status |
/esb-repair |
Create a repair record |
/esb-update |
Update a repair record |
4. Enable Event Subscriptions¶
Under Event Subscriptions:
- Turn on Enable Events
- Set the Request URL to
https://<your-domain>/slack/events - Under Subscribe to bot events, add
message.channels
5. Install the App¶
- Go to Install App and click Install to Workspace
- Authorize the permissions
6. Copy Credentials¶
After installation:
- Copy the Bot User OAuth Token (starts with
xoxb-) and set it asSLACK_BOT_TOKENin your.env - Go to Basic Information and copy the Signing Secret, set it as
SLACK_SIGNING_SECRETin your.env - Restart the app and worker:
docker compose restart app worker
Note
Slack slash commands and event subscriptions require a publicly accessible URL. If your ESB server is on a private network, you'll need a reverse proxy with a public domain or a tunnel service (e.g., ngrok for testing, Cloudflare Tunnel for production).
Static Status Page Setup¶
The static status page provides a lightweight, externally accessible version of the equipment status dashboard. It is regenerated and pushed automatically whenever equipment status changes.
Configuration¶
Set the push method via the STATIC_PAGE_PUSH_METHOD environment variable:
local— Writes the static page to a local directory specified bySTATIC_PAGE_PUSH_TARGET. Useful for serving from a local web server or shared drive.s3— Uploads the static page to an S3 bucket specified bySTATIC_PAGE_PUSH_TARGET. Requires AWS credentials configured in the environment (viaAWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYor an IAM role).gcs— Uploads the static page to a Google Cloud Storage bucket specified bySTATIC_PAGE_PUSH_TARGET. Uses Google's default credential chain (GOOGLE_APPLICATION_CREDENTIALSenvironment variable, GCE instance metadata, or Workload Identity). When using Docker with a service account key file, add a volume mount for the credentials file indocker-compose.yml(e.g.,- ./service-account.json:/app/service-account.json:ro) and setGOOGLE_APPLICATION_CREDENTIALS=/app/service-account.json.
The static page is pushed by the background worker whenever it detects a status change during its polling cycle.
Ongoing Maintenance¶
Viewing Logs¶
# Application logs
docker compose logs -f app
# Worker logs (notification delivery)
docker compose logs -f worker
# Database logs
docker compose logs -f db
Restarting Services¶
# Restart the web application
docker compose restart app
# Restart the notification worker
docker compose restart worker
# Restart all services
docker compose restart
Applying Updates¶
# Pull latest code
git pull
# Rebuild containers
docker compose build
# Restart with new images
docker compose up -d
# Apply any new database migrations
docker compose exec app flask db upgrade
Monitoring the Worker¶
The background worker processes pending notifications every 30 seconds. It includes retry logic with backoff for failed deliveries. Check the worker logs for:
- Successful notification deliveries
- Failed delivery attempts and retry counts
- Slack API errors (usually indicate an expired or invalid token)
docker compose logs -f worker
Upload Storage¶
Uploaded files (equipment photos, documents, diagnostic images) are stored in the ./uploads/ directory, which is bind-mounted into the app container. Monitor disk usage on the host:
du -sh ./uploads/
Database¶
MariaDB data is persisted in the mariadb_data Docker volume. This volume survives container restarts and docker compose down. It is only removed if you explicitly run docker compose down -v (which deletes volumes — do not do this unless you intend to lose all data).
Troubleshooting¶
App won't start¶
- Check that
DATABASE_URLis correct and usesdbas the hostname (notlocalhost) when running in Docker - Verify the
dbservice is healthy:docker compose ps - Check app logs:
docker compose logs app
Slack commands not working¶
- Verify
SLACK_BOT_TOKENandSLACK_SIGNING_SECRETare set correctly in.env - Confirm the Request URL (
https://<your-domain>/slack/events) is reachable from the internet - Check that the Slack App has the required OAuth scopes
- Check app logs for Slack-related errors:
docker compose logs app | grep -i slack
Notifications not delivering¶
- Verify the worker is running:
docker compose ps worker - Check worker logs:
docker compose logs -f worker - Confirm
SLACK_BOT_TOKENis valid and the bot is installed to the workspace - Check that notification triggers are enabled in Admin > Config
Static page not updating¶
- Verify
STATIC_PAGE_PUSH_METHODandSTATIC_PAGE_PUSH_TARGETare set - Check that the worker is running (it handles the push)
- For
s3method: verify AWS credentials and bucket permissions - For
gcsmethod: verify Google Cloud credentials and bucket permissions - For
localmethod: verify the target directory exists and is writable - Check worker logs for push errors