go-migrationGo Migration
Configuration
Documentation

Configuration

Configure go-migration using YAML, JSON, or environment variables — complete reference with all supported fields and default values.

Configuration

go-migration supports configuration through YAML files, JSON files, and environment variables. When using migrator.Run(), the config file defaults to migration.json in the current directory. You can also specify a path with the --config flag.

bash
# Uses migration.json in the current directory (default)
go-migration migrate

# Specify a custom config file
go-migration migrate --config=./config/production.json
go-migration migrate --config=./config/migration.yaml

Configuration format: The default configuration file is migration.json (JSON). The format is auto-detected from the file extension. YAML files (.yaml/.yml) are still supported via the --config flag, but YAML is deprecated — when a YAML file is loaded, go-migration prints a deprecation warning to stderr. Migrate to JSON.

Configuration File

The configuration file defines your database connections, pool settings, migration table name, and logging preferences. All connection details live under a named entry inside the connections map; there are no top-level driver/host/port fields.

Create a migration.json file in your project root:

migration.json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "localhost",
      "port": 5432,
      "database": "myapp",
      "username": "dbuser",
      "password": "dbpass",
      "max_open_conns": 25,
      "max_idle_conns": 5,
      "conn_max_lifetime": "5m",
      "options": {
        "sslmode": "disable"
      }
    }
  },
  "migration_table": "migrations",
  "log_level": "info"
}

Create a migration.yaml file in your project root:

migration.yaml
# Default connection name
default: postgres

connections:
  postgres:
    # Database driver: postgres, mysql, or sqlite
    driver: postgres

    # Connection details
    host: localhost
    port: 5432
    database: myapp
    username: dbuser
    password: dbpass

    # Connection pool settings
    max_open_conns: 25
    max_idle_conns: 5
    conn_max_lifetime: 5m # duration string

    # Driver-specific options (e.g. PostgreSQL sslmode)
    options:
      sslmode: disable

# Migration settings (apply globally)
migration_table: migrations

# Logging
log_level: info

Supported Fields

Top-Level Keys

These are the only keys recognized at the top level of the config file:

FieldTypeRequiredDescription
connectionsmapYesMap of named database connections (see Multi-Connection Configuration)
defaultstringNoName of the default connection to use
migration_tablestringNoName of the migrations tracking table
migration_dirstringNoDirectory for migration files (default: database/migrations)
seeder_dirstringNoDirectory for seeder files (default: database/seeders)
factory_dirstringNoDirectory for factory files (default: database/factories)
log_levelstringNoLogging verbosity — debug, info, warn, error
log_outputstringNoLog destination — console, file, or both

Per-Connection Fields

Each entry under connections.<name> accepts these fields:

FieldTypeRequiredDescription
driverstringYesDatabase driver — postgres, mysql, or sqlite
hoststringYesDatabase server hostname or IP address
portintNoDatabase server port
databasestringYesDatabase name (or file path for SQLite)
usernamestringNoDatabase user
passwordstringNoDatabase password
max_open_connsintNoMaximum number of open connections
max_idle_connsintNoMaximum number of idle connections in the pool
conn_max_lifetimedurationNoMaximum connection lifetime as a Go duration string (e.g. "5m", "300s")
optionsmapNoDriver-specific connection options, e.g. {"sslmode": "disable"} for PostgreSQL

conn_max_lifetime is a Go time.Duration. There is no custom unmarshaler, so a bare number (e.g. 300) is interpreted as nanoseconds, not seconds. Always use a duration string like "5m" or "300s".

Driver-specific parameters such as PostgreSQL's sslmode are not top-level fields. Put them inside the connection's options map. They are appended to the generated DSN.

Environment Variable Fallback

go-migration can build an entire configuration from environment variables. This is a wholesale fallback: when the CLI fails to load the config file (for example, the file does not exist), it falls back to LoadFromEnv(), which constructs a single connection named after GOMIGRATE_DEFAULT_CONNECTION (default default). This is distinct from ${VAR} interpolation, which substitutes individual values inside a config file that does load.

Naming Convention

All recognized environment variables use the GOMIGRATE_ prefix. Connection fields use the GOMIGRATE_DB_ prefix:

Config FieldEnvironment Variable
connection driverGOMIGRATE_DB_DRIVER
connection hostGOMIGRATE_DB_HOST
connection portGOMIGRATE_DB_PORT
connection databaseGOMIGRATE_DB_DATABASE
connection usernameGOMIGRATE_DB_USERNAME
connection passwordGOMIGRATE_DB_PASSWORD
defaultGOMIGRATE_DEFAULT_CONNECTION

There is no environment variable for sslmode or other driver options when loading from env. LoadFromEnv() only reads the fields listed above.

Directory and Logging Settings

LoadFromEnv() also reads these top-level settings from GOMIGRATE_-prefixed variables:

Config FieldEnvironment VariableDefault
migration_dirGOMIGRATE_MIGRATION_DIRdatabase/migrations
seeder_dirGOMIGRATE_SEEDER_DIRdatabase/seeders
factory_dirGOMIGRATE_FACTORY_DIRdatabase/factories
migration_tableGOMIGRATE_MIGRATION_TABLEmigrations
log_levelGOMIGRATE_LOG_LEVELinfo
log_outputGOMIGRATE_LOG_OUTPUTconsole

Resolution Order

The CLI resolves configuration in this order:

  1. Configuration fileconfig.Load() reads and parses the file (with ${VAR} interpolation applied first).
  2. Environment fallback — if the file cannot be loaded, config.LoadFromEnv() builds the config entirely from GOMIGRATE_* variables.
  3. DefaultsApplyDefaults() fills in built-in defaults for any optional field still empty.
bash
# Build the whole config from the environment (used when no config file loads)
export GOMIGRATE_DB_DRIVER=postgres
export GOMIGRATE_DB_HOST=prod-db.example.com
export GOMIGRATE_DB_PORT=5432
export GOMIGRATE_DB_DATABASE=myapp_production
export GOMIGRATE_DB_USERNAME=app_user
export GOMIGRATE_DB_PASSWORD=s3cret

The environment fallback is all-or-nothing for the file: it kicks in only when the config file fails to load. It is not a per-field fallback layered on top of a successfully loaded file. For per-value substitution inside a loaded file, use ${VAR} interpolation instead.

Avoid committing passwords and secrets to version control. Use environment variables or a .env file (excluded from git) for sensitive values.

Default Values

go-migration's ApplyDefaults() fills in these top-level defaults when they are not specified:

SettingDefault ValueDescription
migration_tablemigrationsName of the table that tracks applied migrations
migration_dirdatabase/migrationsDirectory for migration files
seeder_dirdatabase/seedersDirectory for seeder files
factory_dirdatabase/factoriesDirectory for factory files
log_levelinfoLogging verbosity level
log_outputconsoleLog destination

Pool Settings

go-migration sets no pool defaults of its own. It only calls SetMaxOpenConns, SetMaxIdleConns, and SetConnMaxLifetime when the corresponding config value is greater than zero; otherwise the underlying *sql.DB keeps Go's database/sql defaults.

SettingEffective When UnsetSource
max_open_connsunlimited (0)Go database/sql default
max_idle_conns2Go database/sql default — not set by go-migration
conn_max_lifetimeno limit (0)Go database/sql default

For PostgreSQL, the driver defaults sslmode to disable when no sslmode is present in the connection's options map. To change it, set options.sslmode. Pool settings map directly to Go's *sql.DB methods — see Pool Configuration for tuning recommendations.

Driver-Specific Examples

migration.json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "localhost",
      "port": 5432,
      "database": "myapp",
      "username": "postgres",
      "password": "secret",
      "max_open_conns": 25,
      "max_idle_conns": 5,
      "conn_max_lifetime": "5m",
      "options": { "sslmode": "disable" }
    }
  },
  "migration_table": "migrations",
  "log_level": "info"
}
migration.json
{
  "default": "mysql",
  "connections": {
    "mysql": {
      "driver": "mysql",
      "host": "localhost",
      "port": 3306,
      "database": "myapp",
      "username": "root",
      "password": "secret",
      "max_open_conns": 25,
      "max_idle_conns": 5,
      "conn_max_lifetime": "5m"
    }
  },
  "migration_table": "migrations",
  "log_level": "info"
}
migration.json
{
  "default": "sqlite",
  "connections": {
    "sqlite": {
      "driver": "sqlite",
      "database": "./data/myapp.db"
    }
  },
  "migration_table": "migrations",
  "log_level": "info"
}

SQLite uses the database field as the file path. The host, port, username, and password fields are not required. Note that the SQLite driver registers itself under the name sqlite3 for database/sql, while config validation accepts sqlite as the driver value.

Multi-Connection Configuration

The default field specifies which connection is used when no specific connection is requested. The connections field is a map of named database connections, where each key is a connection name and the value contains the connection settings. Every config file uses this structure — connection details always live under connections.<name>.

Directory and table settings (migration_dir, seeder_dir, factory_dir, migration_table, log_level) are defined at the top level and apply globally across all connections.

migration.json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "${DB_HOST}",
      "port": 5432,
      "database": "${DB_DATABASE}",
      "username": "${DB_USERNAME}",
      "password": "${DB_PASSWORD}",
      "options": { "sslmode": "disable" }
    },
    "mysql": {
      "driver": "mysql",
      "host": "${MYSQL_HOST}",
      "port": 3306,
      "database": "${MYSQL_DATABASE}",
      "username": "${MYSQL_USERNAME}",
      "password": "${MYSQL_PASSWORD}"
    }
  },
  "migration_dir": "database/migrations",
  "seeder_dir": "database/seeders",
  "factory_dir": "database/factories",
  "migration_table": "migrations",
  "log_level": "info"
}
migration.yaml
# Default connection name
default: postgres

# Multiple database connections
connections:
  postgres:
    driver: postgres
    host: ${DB_HOST}
    port: 5432
    database: ${DB_DATABASE}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    options:
      sslmode: disable

  mysql:
    driver: mysql
    host: ${MYSQL_HOST}
    port: 3306
    database: ${MYSQL_DATABASE}
    username: ${MYSQL_USERNAME}
    password: ${MYSQL_PASSWORD}

# Directory and settings (apply globally)
migration_dir: database/migrations
seeder_dir: database/seeders
factory_dir: database/factories
migration_table: migrations
log_level: info

When using the --config flag, the structure works the same way:

bash
go-migration migrate --config=./config/migration.json

There is no "flat" configuration format. Connection details (driver, host, port, etc.) must be nested under connections.<name>. Placing them at the top level has no effect, since the Config struct only recognizes connections, default, migration_table, migration_dir, seeder_dir, factory_dir, log_level, and log_output at the top level.

Environment Variable Interpolation

You can embed environment variable references directly in your YAML or JSON config values using ${VAR_NAME} placeholders. This is different from the environment variable fallback — interpolation lets you compose values from multiple environment variables inside a config file that loads successfully.

migration.yaml
default: postgres
connections:
  postgres:
    driver: postgres
    host: ${DB_HOST}
    port: 5432
    database: ${DB_NAME}
    username: ${DB_USER}
    password: ${DB_PASSWORD}
    options:
      sslmode: ${DB_SSLMODE}
log_level: info

How It Works

Interpolation happens after reading the file and before YAML/JSON parsing:

  1. go-migration reads the raw config file content
  2. All ${VAR_NAME} placeholders are replaced with the corresponding environment variable values
  3. The resulting string is then parsed as YAML or JSON

Auto-Unquote for JSON (v1.0.0)

When using JSON config files, InterpolateEnv automatically removes surrounding quotes for values that resolve to numeric, boolean (true/false), or null types. This means your JSON stays valid with correct types after interpolation.

migration.json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "${DB_HOST}",
      "port": "${DB_PORT}",
      "database": "${DB_NAME}",
      "options": { "sslmode": "disable" }
    }
  }
}

With DB_HOST=localhost, DB_PORT=5432, and DB_NAME=myapp, the result after interpolation is:

json
{
  "default": "postgres",
  "connections": {
    "postgres": {
      "driver": "postgres",
      "host": "localhost",
      "port": 5432,
      "database": "myapp",
      "options": { "sslmode": "disable" }
    }
  }
}

Notice that "port": "${DB_PORT}" becomes "port": 5432 (a number, not the string "5432"). The same applies to boolean and null values:

Environment VariableJSON BeforeJSON After
DB_PORT=5432"port": "${DB_PORT}""port": 5432
ENABLE_SSL=true"ssl": "${ENABLE_SSL}""ssl": true
EXTRA=null"extra": "${EXTRA}""extra": null
DB_HOST=localhost"host": "${DB_HOST}""host": "localhost"

Auto-unquote applies only to JSON files. In YAML files, data types are handled natively by the YAML parser, so no unquoting is needed.

Escaping

To produce a literal ${VAR_NAME} in your config (without interpolation), use the double-dollar escape sequence:

migration.yaml
connections:
  postgres:
    # This resolves to the value of DB_HOST
    host: ${DB_HOST}
    # This produces the literal string "${NOT_REPLACED}"
    password: $${NOT_REPLACED}

Missing Variables

If any ${VAR_NAME} references an environment variable that is not set, go-migration returns an error listing all unresolved variables. This prevents silent misconfiguration.

All unresolved ${VAR_NAME} placeholders are reported in a single error message, so you can fix them all at once rather than one at a time.

Config Validation

go-migration validates your configuration at load time and reports all violations in a single error. This catches common mistakes before any database operations are attempted.

Validated Fields

FieldRule
driverMust be postgres, mysql, or sqlite
portMust be a positive number when specified
log_levelMust be debug, info, warn, or error
log_outputMust be console, file, or both
max_open_connsMust be non-negative
max_idle_connsMust be non-negative

Example Error

If multiple fields are invalid, you get a single error with all violations joined together:

configuration validation failed: connections.postgres.driver must be one of: postgres, mysql, sqlite, log_level must be one of: debug, info, warn, error

Validation runs after environment variable interpolation and fallback resolution, so the final resolved values are what gets validated.

Programmatic Usage

The config package is publicly available at pkg/config, so you can import it directly in your own Go projects — no need to reimplement config loading, validation, or environment variable interpolation.

main.go
package main

import (
	"fmt"
	"log"

	"github.com/gopackx/go-migration/pkg/config"
)

func main() {
	// Load configuration from a JSON (or deprecated YAML) file
	cfg, err := config.Load("migration.json")
	if err != nil {
		log.Fatalf("failed to load config: %v", err)
	}

	// Apply default values for optional fields
	cfg.ApplyDefaults()

	// Validate the resolved configuration
	if err := cfg.Validate(); err != nil {
		log.Fatalf("invalid config: %v", err)
	}

	// Connection details live under the connections map.
	conn := cfg.Connections[cfg.DefaultConn]
	fmt.Printf("Connected to %s on %s:%d\n", conn.Database, conn.Host, conn.Port)
}

config.Load() handles file reading, YAML/JSON parsing, and environment variable interpolation (${VAR_NAME}) in a single call. Follow it with ApplyDefaults() and Validate() for a fully resolved and validated configuration.

What's Next?