Disco is a CLI tool for managing customer database migrations for Discourse. It handles Docker containers for source databases, converters, and Discourse staging/production environments.
# Build from source
cd disco
make build
# Install to GOPATH/bin
make install
# Or install globally
sudo make install-global- Docker running
- Go toolchain (to build disco)
- On macOS: set
DISCO_MIGRATIONS_DIR— the default/migrationsis on a read-only volume
# 0. Build disco (if not installed)
cd disco && make build
# 1. (macOS) Override migrations directory
export DISCO_MIGRATIONS_DIR="$HOME/migrations"
# 2. Set up a new customer
disco setup acme-corp --db-type mariadb
# 3. Place your database dump
cp /path/to/dump.sql.gz $DISCO_MIGRATIONS_DIR/acme-corp/data/dumps/v1/
# 4. Start the source database (wait for healthy)
disco start acme-corp --profile source-db
# 5. Set up the converter
disco setup-converter acme-corp
# 6. Build and start the converter container
disco start acme-corp --profile converter --build
# 7. (Dev only) Install disco-rb gem into the converter container
cd disco-rb && gem build disco-rb.gemspec
docker cp disco-rb-*.gem <converter-container>:/tmp/
docker exec <converter-container> gem install /tmp/disco-rb-*.gem --no-document
# 8. Import the database dump (smart import with streaming transforms)
disco db import acme-corp
# Or with turbo mode: disco db import acme-corp --turbo
# (fallback: disco exec acme-corp db restore current/dump.sql.gz)
# 9. Run the conversion
disco exec acme-corp convert <platform>
# 10. Check conversion results
disco exec acme-corp status
disco exec acme-corp validate
# 11. (Optional) Add staging environment and import
disco add-env acme-corp staging
disco start acme-corp --profile staging
disco exec acme-corp:staging import
# Cleanup
disco stop acme-corp
disco destroy acme-corp --yesNote: Step 7 is only needed during development while the disco-rb gem is not published. Once published, the converter Dockerfile will install it automatically.
The v2 converter uses the core migrations framework built into the Discourse image. No disco-rb gem is needed.
# 0. Build disco (if not installed)
cd disco && make build
# 1. (macOS) Override migrations directory
export DISCO_MIGRATIONS_DIR="$HOME/migrations"
# 2. Set up a new customer with v2 converter
disco setup acme-corp --db-type mariadb --converter-version v2
# 3. Place your database dump
cp /path/to/dump.sql.gz $DISCO_MIGRATIONS_DIR/acme-corp/data/dumps/v1/
# 4. Start the source database (wait for healthy)
disco start acme-corp --profile source-db
# 5. Set up the converter (generates settings.yml, disco-db script)
disco setup-converter acme-corp
# 6. Build and start the converter container
# (--build needed for MySQL/MariaDB sources; PostgreSQL uses base image)
disco start acme-corp --profile converter --build
# 7. Import the database dump (smart import with streaming transforms)
disco db import acme-corp
# Or with turbo mode: disco db import acme-corp --turbo
# (fallback: disco exec acme-corp db restore current/dump.sql.gz)
# 8. Run the conversion
disco exec acme-corp convert <platform>
# 9. Check conversion results
disco exec acme-corp status
disco exec acme-corp validate
# 10. (Optional) Add staging environment and import
disco add-env acme-corp staging
disco start acme-corp --profile staging
disco exec acme-corp:staging import
# Cleanup
disco stop acme-corp
disco destroy acme-corp --yes| Command | Description |
|---|---|
disco setup <customer> |
Initialize a new migration project (--converter-version v1|v2, --wizard for interactive mode) |
disco list |
List all customer projects with status and converter version |
disco status <customer> |
Show detailed status for a customer |
disco destroy <customer> |
Remove all containers and data for a customer |
| Command | Description |
|---|---|
disco start <customer> |
Start containers (use --profile for specific services) |
disco stop <customer> |
Stop all containers |
disco exec <customer> <cmd> |
Execute command in converter container |
disco exec <customer>:<env> <cmd> |
Execute command in specific environment |
| Command | Description |
|---|---|
disco setup-converter <customer> |
Set up converter (v1: clone repo, v2: generate config) |
disco add-env <customer> <env> |
Add staging or production environment |
disco regenerate <customer> |
Regenerate docker-compose.yml from config |
| Command | Description |
|---|---|
disco db-ports |
Show all port allocations |
disco ssh-config |
Generate SSH tunnel configuration |
disco version |
Show version information |
| Command | Description |
|---|---|
disco db import <customer> |
Import dump with streaming transforms (--fast, --turbo, --fix-collations, --exclude-tables) |
disco db replace <customer> |
Reset database and re-import (--yes to skip prompt) |
disco db reset <customer> |
Drop and recreate source database |
disco db console <customer> |
Open interactive mysql/psql console |
disco db analyze <customer|file> |
Analyze dump: table sizes, row counts (--full for complete scan) |
The disco db import command provides a smart, streaming import pipeline that replaces the basic disco exec <customer> db restore command.
- Default — Standard import, no modifications
--fast— Disables key checks and unique checks for faster loading--turbo— Fast mode plus enlarged packet sizes for maximum throughput
When importing MySQL 8 dumps into MariaDB, disco db import auto-detects incompatible collations (e.g. utf8mb4_0900_ai_ci) and replaces them with MariaDB-compatible equivalents. Override with --fix-collations or --no-fix-collations.
Skip large or unnecessary tables during import:
disco db analyze acme-corp # See table sizes and row counts
disco db import acme-corp --exclude-tables sessions,cache_entriesThe existing disco exec <customer> db restore command still works as a simple fallback for cases where the streaming pipeline is not needed.
Use the --wizard flag for an interactive setup experience:
# Full interactive mode
disco setup --wizard
# With dump file for auto-detection
disco setup acme-corp --wizard --dump /path/to/dump.sql.gz
# Auto-detection without wizard (uses detected type directly)
disco setup acme-corp --dump /path/to/dump.sql.gzThe wizard will:
- Auto-detect database type from dump file signatures
- Guide you through customer name and database type selection
- Highlight recommended options based on detection
- Confirm configuration before creating
Disco uses Docker Compose profiles to selectively start services:
source-db- Source database onlyconverter- Converter container onlystaging- Staging Discourse environment (db, redis, discourse)production- Production Discourse environmentall- All enabled services
Example:
disco start acme-corp --profile source-db
disco start acme-corp --profile staging
disco start acme-corp # Starts all enabled servicesEach customer gets an isolated directory. The layout differs by converter version:
v1 customer:
/migrations/<customer>/
├── config.env # Customer configuration
├── docker-compose.yml # Generated compose file
├── config/
│ ├── staging/settings.yml
│ └── production/settings.yml
├── data/dumps/
│ ├── v1/ # First dump from customer
│ ├── v2/ # Second dump (if customer sends corrections)
│ └── current -> v1/ # Symlink to active version
├── uploads/
│ ├── source/ # Raw uploads from customer
│ └── processed/ # Ready for import
├── output/ # Converter output (intermediate.db)
├── discourse-converters/ # Cloned converter repo (v1 only)
└── logs/
v2 customer:
/migrations/<customer>/
├── config.env # Customer configuration (CONVERTER_VERSION=v2)
├── docker-compose.yml # Generated compose file
├── config/
│ ├── converter/
│ │ ├── settings.yml # Converter settings (generated)
│ │ ├── disco-db # DB helper script (generated)
│ │ └── Dockerfile # Custom image (MySQL/MariaDB only)
│ ├── staging/settings.yml
│ └── production/settings.yml
├── data/dumps/
│ ├── v1/ # First dump from customer
│ └── current -> v1/ # Symlink to active version
├── uploads/
│ ├── source/ # Raw uploads from customer
│ └── processed/ # Ready for import
├── output/ # Converter output (intermediate.db)
└── logs/
Disco automatically allocates ports in the 15000-16000 range:
- Each customer gets a block of 10 contiguous ports
- Port offsets:
- +0: Source database
- +1: Staging web
- +2: Staging database
- +3: Staging Redis
- +4: Production web
- +5: Production database
- +6: Production Redis
View allocations with disco db-ports.
Global settings are stored in ~/.config/disco/config.json:
{
"port_range_start": 15000,
"port_range_end": 16000,
"ports_per_customer": 10,
"converters_repo": "git@github.com:discourse-org/discourse-converters.git"
}Each customer has a config.env file:
# Converter version (v1 = discourse-converters, v2 = core migrations framework)
# Omitted or empty defaults to v1
CONVERTER_VERSION=v2
# Source database configuration
SOURCE_DB_TYPE=mariadb
SOURCE_DB_PORT=15000
SOURCE_DB_NAME=acme_source_db
SOURCE_DB_PASSWORD=<randomly-generated>
# Staging environment (optional)
STAGING_ENABLED=true
STAGING_WEB_PORT=15001
STAGING_DB_PORT=15002
STAGING_REDIS_PORT=15003
# Production environment (optional)
PRODUCTION_ENABLED=false
# Advanced options (v2 only)
# DISCOURSE_PATH=/path/to/local/discourse # Mount local Discourse for developmentNote: Passwords are randomly generated when creating new customers for improved security.
Disco supports two converter versions. Both can coexist on the same server (version is per-customer).
v1 (default): Uses the discourse-converters repository cloned into each customer directory. Commands are routed through wrapper scripts and the disco-rb gem inside the converter container. Requires disco-rb installation.
v2: Uses the core migrations framework built into the discourse/discourse_dev:release Docker image. Commands are translated by Go directly to migrations/bin/cli inside the container. No disco-rb or external converter repo needed.
| Aspect | v1 | v2 |
|---|---|---|
| Converter source | discourse-converters repo |
Core Discourse migrations framework |
| Container image | Built from discourse-converters/Dockerfile |
discourse/discourse_dev:release (+ custom Dockerfile for MySQL/MariaDB) |
| Command routing | Wrapper scripts -> disco-rb -> converter | Go translates -> migrations/bin/cli |
setup-converter creates |
Cloned git repo | settings.yml, disco-db script, optional Dockerfile |
| disco-rb required | Yes | No |
DISCO_VERBOSE- Enable verbose output (set to1,true,yes, oron)DISCO_MIGRATIONS_DIR- Override base migrations directory (default:/migrations)
Generate SSH config for remote access:
disco ssh-config --host migration-server.example.com >> ~/.ssh/config
# Then connect:
ssh disco-acme-corp-db- MariaDB 10.11 - Default for MySQL dumps
- MySQL 8.0 - For MySQL 8+ specific dumps
- PostgreSQL 16 - For PostgreSQL dumps
# Bash
disco completion bash > /etc/bash_completion.d/disco
# Zsh
disco completion zsh > "${fpath[1]}/_disco"
# Fish
disco completion fish > ~/.config/fish/completions/disco.fishcd disco && make testExercises the full converter pipeline (requires Docker and the discourse-converters repo):
bash test/e2e/run.shVerifies that the Go compose generator and Ruby config module agree on environment variable names:
bash test/contract/check_env_vars.shStatus and Validate commands tested against SQLite fixtures:
cd disco-rb && bundle exec rake integration