Docker support is designed for mentor-friendly deployment and reproducible runs without changing Gitcord’s offline-first architecture. The bot and run-once both work; SQLite and reports persist across restarts.
For GitHub PAT setup, Discord bot creation, and first-time configuration, start with INSTALLATION.md. This document covers Docker-specific details.
- No local Python/setup: Mentors run
docker compose upafter adding.envand config. - Same behavior as CLI: Same code paths; only the runtime is containerized.
- Persistent state: Named volume keeps SQLite, reports, and identity links across restarts.
- Audit-first unchanged: Dry-run and reports work the same; config and mutation policy are unchanged.
-
Create
.envin the project root (copy from.env.example):cp .env.example .env
Edit
.envand set your tokens:GITHUB_TOKEN=your_fine_grained_pat DISCORD_TOKEN=your_discord_bot_token
-
Create config (use Docker-specific data dir):
cp config/docker-example.yaml config/config.yaml
Edit
config/config.yaml: setgithub.org,discord.guild_id, and any other options. Do not changedata_dir; it must stay/dataso the mounted volume is used.Remote org config (optional): mentors can keep non-secret settings in the org’s
.githubrepo (gitcord.yaml) so every Gitcord instance shares one source of truth. Secrets stay in.env.# Example AOSSIE bootstrap (tokens still from .env) cp config/examples/bootstrap-remote-aossie.yaml config/config.yaml # Publish org settings once: # config/examples/remote-gitcord-aossie.yaml → AOSSIE-Org/.github/gitcord.yaml
On startup Gitcord fetches the remote YAML, caches it under
/data/remote_config_cache.yaml, and expands${GITHUB_TOKEN}/${DISCORD_TOKEN}from.env. If GitHub is temporarily unreachable, the last cache is used. Setremote_config.refto the.githubrepo’s default branch (masterfor AOSSIE-Org/.github,mainfor StabilityNexus/.github), or omitrefto use the repo default. -
Start the bot:
docker compose up -d docker compose logs -f bot
The Discord bot runs in the background. Slash commands sync within ~30 seconds.
Validate setup (read-only, before first sync):
docker compose run --rm bot --config /app/config/config.yaml validateChecks config, tokens, GitHub/Discord API access, guild, and role names. Exit code 0 = ready; 1 = fix reported issues.
Run a one-off sync (dry-run or active):
docker compose run --rm bot --config /app/config/config.yaml run-onceThe image ENTRYPOINT is ghdcbot, so you only pass subcommand arguments after the service name.
Gitcord-GithubDiscordBot/
├── .env # Tokens (never commit; not in image)
├── .env.example
├── config/
│ ├── config.yaml # Your active config (create from docker-example.yaml; gitignored)
│ ├── docker-example.yaml # Template for Docker
│ ├── example.yaml # Template for local install
│ └── examples/ # Reference configs (not used by compose)
├── docker-compose.yml
├── docker-compose.instance.example.yml # Template for a second org instance
├── Dockerfile
├── pyproject.toml
├── src/
└── ...
Inside the container:
/app= app root (code, config mount at/app/config)./data= persistent volume (SQLitestate.db,reports/,audit_events.jsonl). Setdata_dir: "/data"in config.
Gitcord is designed for any open-source org. To run more than one org/server on the same host, use isolated stacks — never share .env, config, or the SQLite volume.
For each org:
-
Create a new Discord application (own bot token) and upload
public/gitcord-discord-icon-large.pngas App Icon + Bot Icon (INSTALLATION.md). -
Create a separate GitHub PAT with access to that org.
-
Copy templates and fill placeholders:
cp .env.example .env.myorg cp config/docker-example.yaml config/myorg.local.yaml cp docker-compose.instance.example.yml docker-compose.myorg.local.yml
-
Edit
config/myorg.local.yaml(github.org,discord.guild_id, channels, notifications) and the copied compose file (env_file, config path, volume name). -
Start with a unique project name:
docker compose -p gitcord-myorg -f docker-compose.myorg.local.yml --env-file .env.myorg up -d --build docker compose -p gitcord-myorg -f docker-compose.myorg.local.yml --env-file .env.myorg --profile scheduler up -d
*.local.yaml and docker-compose.*.local.yml are gitignored so live org IDs stay off the public repo.
| Section | Purpose |
|---|---|
FROM python:3.11-slim |
Matches requires-python = ">=3.11"; slim reduces image size and attack surface. |
PYTHONDONTWRITEBYTECODE=1 |
Avoids writing .pyc in the image; cleaner and slightly faster. |
PYTHONUNBUFFERED=1 |
Logs show up immediately in docker compose logs. |
Copy pyproject.toml + src/ then pip install -e . |
Dependency layer is cached; only code/setup changes trigger reinstall. |
useradd appuser / USER appuser |
Process runs as non-root. |
ENTRYPOINT ["ghdcbot"] |
Lets docker compose run bot --config … run-once work without repeating the binary name. |
CMD ["--config", "/app/config/config.yaml", "bot"] |
Default is Discord bot; override args for run-once etc. |
| Section | Purpose |
|---|---|
init_data service |
Runs once as root to chown the volume to appuser so the bot (non-root) can write; then exits. Bot starts after it completes. |
env_file: .env |
Loads GITHUB_TOKEN and DISCORD_TOKEN; config YAML uses ${GITHUB_TOKEN} etc. |
./config:/app/config:ro |
Host config dir mounted read-only; edit YAML on host without rebuilding. |
gitcord_data:/data |
Named volume for SQLite and reports; survives docker compose down. |
command: ["--config", "/app/config/config.yaml", "bot"] |
Ensures config path is correct and default is bot. |
restart: unless-stopped |
Bot comes back after reboot or Docker restart. |
| Pitfall | Cause | Fix |
|---|---|---|
| "Config file does not exist" | No config/config.yaml or wrong path. |
Copy config/docker-example.yaml to config/config.yaml and keep data_dir: "/data". |
| "Missing required environment variable: GITHUB_TOKEN" | .env missing or not loaded. |
Create .env in project root (same dir as docker-compose.yml) with GITHUB_TOKEN and DISCORD_TOKEN. |
| State lost after restart | data_dir pointed at a non-persistent path. |
Use data_dir: "/data" and the provided docker-compose volume; do not override /data with a host path unless you intend to. |
| Bot doesn’t respond / "application did not respond" | Same as non-Docker: slow storage or missing intents. | Ensure Server Members Intent is enabled; check logs with docker compose logs -f. |
Permission errors on /data |
Container user cannot write. | Dockerfile already runs as appuser; the volume is writable by the container. If you use a host bind mount for data, ensure the host dir is writable (e.g. chown to the same UID as appuser). |
| Running both bot and run-once | Need two invocations. | Bot: docker compose up -d. Run-once: docker compose run --rm bot --config /app/config/config.yaml run-once. |
- Keep
runtime.mode: "dry-run"in config. - Run once:
docker compose run --rm bot --config /app/config/config.yaml run-once - Inspect reports in the volume (e.g. copy out or run a temporary container that mounts the same volume and cats the file):
Reports are under/data/reports/(e.g.audit.md,audit.json). - When satisfied, set
runtime.mode: "active",runtime.enable_discord_role_updates: true, anddiscord.permissions.write: truein config, then runrun-onceagain or let the bot apply changes on the next sync.
The Discord bot handles slash commands; background sync ingests GitHub activity, sends notifications, and (when enabled) updates roles. Run it on a schedule so mentors do not need /sync every time.
Prerequisites: for a large first ingest, set discord.notifications.enabled: false once, run run-once, then re-enable notifications in config/config.yaml (from config/docker-example.yaml).
Safety: every run-once (CLI, /sync, scheduler) runs a preflight that aborts if assignments.issue_assignees or assignments.review_roles are set while github.permissions.write: true — this blocks the bulk auto-assign incident. Check manually:
./scripts/preflight-sync.shRuns run-once every 6 hours in a sidecar container (same SQLite volume as the bot):
cp config/aussie.yaml config/config.yaml # if not already done
docker compose up -d # bot only
docker compose --profile scheduler up -d # bot + sync-scheduler
docker compose logs -f sync-schedulerChange interval in .env (seconds):
GITCORD_SYNC_INTERVAL_SECONDS=43200 # 12 hoursStop scheduler only: docker compose --profile scheduler stop sync-scheduler
For VPS hosts that already use cron:
chmod +x scripts/scheduled-run-once.sh
# Edit path in deploy/cron/gitcord-sync.crontab, then:
crontab -eExample line (every 6 hours):
0 */6 * * * cd /path/to/Gitcord-GithubDiscordBot && ./scripts/scheduled-run-once.sh >> /var/log/gitcord-sync.log 2>&1Overlapping runs are skipped via a lock file on the /data volume.
docker compose run --rm bot --config /app/config/config.yaml run-once- Reproducibility: Same image and config produce the same behavior; use tagged images if you need to pin versions.
- Secrets: Never bake tokens into the image; use
.envor a secrets manager andenv_file/ env. - Updates: Rebuild with
docker compose build --no-cacheafter dependency or code changes; config and data are unchanged. - Logs: Use
docker compose logs -f botfor live logs; log level is controlled by configruntime.log_level. - Scheduled sync: Use
sync-schedulerprofile or host cron; with 15 AOSSIE repos expect 10–30+ minutes per run.