Deploying file changes is the straightforward part. The harder question is: what happens when your code update requires a database schema change? A new column, a renamed table, or a modified index can't be deployed the same way you upload files — and getting the timing wrong can take your application offline.

Database migrations solve this by versioning your schema changes alongside your code. Each migration is a file that describes a specific change and can be applied (or rolled back) in order. When paired with DeployHQ's [SSH commands](https://www.deployhq.com/support/ssh-commands), migrations run automatically as part of every deployment.

## What are database migrations?

A database migration is a version-controlled script that modifies your database schema. Instead of manually running SQL statements against production, you write migration files that define the changes declaratively. A migration tool tracks which migrations have already been applied and runs only the new ones.

```
flowchart LR
    A[Push code + migration files] --> B[DeployHQ uploads files]
    B --> C[SSH command runs migrations]
    C --> D[Database updated]
    D --> E[Application serves new code]
```

This approach gives you:

- **Version control for your schema** : Every change is tracked in Git, with a clear history of who changed what and when
- **Reproducible environments** : Run the same migrations against development, staging, and production to keep them in sync
- **Safe rollbacks** : Most migration tools generate reverse migrations, so you can undo changes if something goes wrong
- **Team coordination** : Multiple developers can create migrations independently, and the tool handles ordering and conflict detection

## Migration tools by framework

Most web frameworks include migration support. Here's how the most common ones work:

### Laravel (PHP)

Laravel's Artisan CLI is one of the most widely used migration tools. To create a migration:

```
php artisan make:migration create_orders_table
```

This generates a timestamped file in `database/migrations/`:

```
class CreateOrdersTable extends Migration
{
    public function up()
    {
        Schema::create('orders', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained();
            $table->decimal('total', 10, 2);
            $table->string('status')->default('pending');
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('orders');
    }
}
```

Run migrations with:

```
php artisan migrate
```

For a complete guide to deploying Laravel applications, see our [Laravel deployment guide](https://www.deployhq.com/blog/how-to-deploy-laravel-zero-downtime-build-pipelines-and-best-practices).

### Ruby on Rails

Rails uses Active Record migrations. Create one with:

```
rails generate migration CreateOrders user:references total:decimal status:string
```

Run migrations with:

```
rails db:migrate
```

### Django (Python)

Django auto-generates migrations from model changes:

```
python manage.py makemigrations
python manage.py migrate
```

### Prisma (Node.js / TypeScript)

Prisma uses a schema-first approach. Define your models in `schema.prisma`, then generate and apply migrations:

```
npx prisma migrate dev --name add_orders_table
npx prisma migrate deploy # For production
```

### .NET Entity Framework

EF Core generates migrations from your C# model classes:

```
dotnet ef migrations add CreateOrders
dotnet ef database update
```

For a detailed walkthrough, see our guide on [automating database migrations in .NET with DbUp](https://www.deployhq.com/blog/automating-database-migrations-in-net-a-complete-guide-to-using-deployhq-with-dbup).

### Flyway (Java / any language)

[Flyway](https://www.deployhq.com/blog/master-your-database-migrations-with-flyway-a-comprehensive-guide-for-all-projects) uses plain SQL files with a naming convention:

```
V1__create_users.sql
V2__create_orders.sql
V3__add_status_to_orders.sql
```

Run with:

```
flyway migrate
```

## Setting up migrations in DeployHQ

Once your migration files are committed to your repository, configure [DeployHQ](https://www.deployhq.com) to run them automatically during deployment using SSH commands.

Navigate to **SSH Commands** in your project sidebar and add a command set to run **after files are transferred** :

![SSH command for running migrations](https://blog.deployhq.com/attachment/9b25b0a9-b561-45a8-ac89-ef3e152a8b35/Cf-Q4YJY.png)

For a Laravel project, the command would be:

```
cd /var/www/your-app/current && php artisan migrate --force
```

The `--force` flag is required in production to skip the confirmation prompt. For Rails:

```
cd /var/www/your-app/current && RAILS_ENV=production bundle exec rails db:migrate
```

The migration command should be idempotent — if there are no pending migrations, it completes immediately without making changes.

## Rollback strategies

Things go wrong. A migration might fail partway through, or the new code might not work as expected with the schema changes. Here's how to handle it:

### Automatic rollback with atomic deployments

When using DeployHQ's [zero downtime deployments](https://www.deployhq.com/blog/setting-up-zero-downtime-deployments), a failed deployment leaves the previous release in place. However, if the migration has already run, your database schema won't match the old code.

To handle this safely:

1. **Make migrations backwards-compatible** when possible — add new columns as nullable, don't rename columns in the same deployment as the code change
2. **Use a two-phase approach** for breaking changes: deploy the migration first (adding the new column), then deploy the code that uses it in a separate release
3. **Test migrations against a staging database** before running them in production

### Manual rollback

Most migration tools support rolling back the last batch of migrations:

```
# Laravel
php artisan migrate:rollback

# Rails
rails db:rollback

# Django
python manage.py migrate app_name 0003_previous_migration

# Prisma
# Prisma doesn't support automatic rollback — revert manually or use a previous migration
```

For a deeper look at deployment safety, see our guide on [zero downtime database migration strategies](https://www.deployhq.com/blog/database-migration-strategies-for-zero-downtime-deployments-a-step-by-step-guide).

## Best practices

- **Never edit a migration that's already been applied** in production. Create a new migration instead.
- **Keep migrations small and focused**. One migration per logical change makes rollbacks simpler.
- **Always include a `down` method** (or equivalent) so migrations can be reversed.
- **Test migrations on a copy of production data** before deploying. Schema changes that work on an empty database may fail on one with millions of rows.
- **Monitor migration duration**. Long-running migrations (like adding an index to a large table) may need to run outside the deployment process using online DDL techniques.
- **Use [config files](https://www.deployhq.com/support/config-files)** for database credentials rather than hardcoding them in your repository.

* * *

Ready to automate your deployments, including database migrations? [Sign up for DeployHQ](https://www.deployhq.com/signup) and run your first deployment in minutes.

If you have any questions about database migrations or any other aspect of [DeployHQ](https://www.deployhq.com), contact us at [support@deployhq.com](mailto:support@deployhq.com) or reach out on [X (Twitter)](https://x.com/deployhq).

