<!-- OSSAlt AI-readable guide source -->
<!-- Canonical: https://ossalt.com/guides/postgresql-vs-mysql-vs-mariadb-self-hosting-2026 -->
<!-- Raw Markdown: https://ossalt.com/guides/postgresql-vs-mysql-vs-mariadb-self-hosting-2026/raw.md -->
<!-- Source path: content/guides/postgresql-vs-mysql-vs-mariadb-self-hosting-2026.mdx -->

---
og_image: "/images/guides/postgresql-vs-mysql-vs-mariadb-self-hosting-2026.webp"
title: "PostgreSQL vs MySQL vs MariaDB 2026"
description: "PostgreSQL, MySQL, and MariaDB compared for self-hosted deployments in 2026. Features, performance, Docker setup, and which database to choose by use case."
date: "2026-03-09"
author: "OSSAlt Team"
tags: ["postgresql", "mysql", "mariadb", "database", "self-hosting", "docker", "2026"]
---

## TL;DR

For new self-hosted projects in 2026: **use PostgreSQL**. It has the most advanced features (JSONB, arrays, extensions, full-text search), excellent performance, true open source governance, and is the default for most modern frameworks. **MySQL** is a solid choice for legacy PHP applications that explicitly require it. **MariaDB** is the best MySQL alternative if you need MySQL compatibility without Oracle dependency — better governance, Galera replication, and drop-in compatibility.

## Key Takeaways

- **PostgreSQL**: Most advanced SQL features, JSONB, PostGIS, true open source, best for modern apps
- **MySQL 8.x**: Oracle-owned (GPL), massive ecosystem, best for WordPress/legacy PHP, 8.4 LTS
- **MariaDB**: MySQL fork by original MySQL creator, GPL, drop-in compatible, better governance, Galera clustering
- **Performance**: All three handle typical self-hosted workloads comfortably; differences matter at high scale
- **Docker**: All have official Alpine-based images — PostgreSQL is ~78MB, MySQL ~565MB, MariaDB ~403MB
- **Governance**: PostgreSQL (community) > MariaDB (foundation) > MySQL (Oracle)

---

## Quick Comparison

| Feature | PostgreSQL 16 | MySQL 8.4 | MariaDB 11.4 |
|---------|--------------|-----------|--------------|
| License | PostgreSQL License | GPL 2.0 (dual) | GPL 2.0 |
| Governance | Community | Oracle | MariaDB Foundation |
| JSONB / JSON | ✅ JSONB (binary, indexed) | JSON (text) | JSON (text) |
| Arrays | ✅ Native | ❌ | ❌ |
| Window functions | ✅ Full | ✅ | ✅ |
| CTEs | ✅ (recursive) | ✅ | ✅ |
| Full-text search | ✅ Native + tsvector | ✅ | ✅ |
| Extensions | ✅ PostGIS, pgvector, etc. | ❌ | ❌ |
| ACID compliance | ✅ Full | ✅ InnoDB | ✅ InnoDB/Aria |
| Replication | Streaming, logical | Binary, GTID | Binary + Galera |
| Multi-master | Via Citus extension | InnoDB Cluster | ✅ Galera native |
| Default port | 5432 | 3306 | 3306 |
| Docker image size | ~78MB (alpine) | ~565MB | ~403MB |
| Best for | Modern apps, complex queries | WordPress, legacy PHP | MySQL replacement |

---

## PostgreSQL: The Default Choice for Modern Apps

[PostgreSQL](https://postgresql.org) is the most feature-complete open source relational database. Governed by the PostgreSQL Global Development Group (community-owned, no corporate control), it implements more of the SQL standard than any other open source database.

### Why PostgreSQL for New Projects

**JSONB support.** Postgres stores JSON as binary (JSONB), enabling full indexing and efficient querying:

```sql
-- Store JSON documents efficiently:
CREATE TABLE events (
  id BIGSERIAL PRIMARY KEY,
  data JSONB NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Index specific JSON fields:
CREATE INDEX idx_events_user ON events((data->>'user_id'));

-- Query JSON:
SELECT data->>'user_id', COUNT(*)
FROM events
WHERE data->>'event_type' = 'purchase'
  AND (data->>'amount')::numeric > 100
GROUP BY 1;
```

**Extensions.** PostgreSQL has a rich extension ecosystem:
- **pgvector**: Vector similarity search for AI/ML applications
- **PostGIS**: Geospatial queries (closest locations, within radius, etc.)
- **pg_trgm**: Fuzzy text search (autocomplete, typo tolerance)
- **TimescaleDB**: Time-series data optimization
- **Citus**: Horizontal scaling / sharding

**Better standards compliance.** PostgreSQL implements more of the SQL standard, resulting in more portable queries.

### Docker Setup

```yaml
services:
  postgres:
    image: postgres:16-alpine    # 78MB vs 565MB for full MySQL image
    restart: unless-stopped
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U myapp"]
      interval: 10s
      timeout: 5s
      retries: 5
```

### Common Operations

```bash
# Connect:
docker exec -it postgres psql -U myapp

# Backup:
docker exec postgres pg_dump -U myapp myapp | gzip > backup.sql.gz

# Restore:
gunzip -c backup.sql.gz | docker exec -i postgres psql -U myapp -d myapp
```

---

## MySQL 8.x: The PHP Ecosystem Standard

[MySQL](https://mysql.com) remains the world's most deployed relational database. Its ubiquity in the PHP ecosystem (WordPress, Drupal, Joomla, Laravel) means you'll encounter it constantly.

### When to Choose MySQL

- **WordPress / Drupal / Joomla**: These CMS platforms are optimized for MySQL and officially recommend it
- **Legacy PHP applications**: Codebases written against MySQL-specific behavior
- **Existing MySQL infrastructure**: No reason to switch if it works
- **Managed MySQL services**: RDS, PlanetScale, Railway all offer managed MySQL

### License Warning

MySQL's GPL 2.0 is a dual license — the Community Edition is free, but Oracle can (and does) restrict commercial use in certain ways. If you're building a commercial product and want simpler licensing: use PostgreSQL (permissive) or MariaDB (GPL without the Oracle dependency).

### Docker Setup

```yaml
services:
  mysql:
    image: mysql:8.0
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: myapp
      MYSQL_USER: myapp
      MYSQL_PASSWORD: "${MYSQL_PASSWORD}"
      MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}"
    volumes:
      - mysql_data:/var/lib/mysql
    ports:
      - "3306:3306"
    command: --default-authentication-plugin=mysql_native_password
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
```

### Common Operations

```bash
# Connect:
docker exec -it mysql mysql -u myapp -p myapp

# Backup (mysqldump):
docker exec mysql mysqldump -u myapp -pmypassword myapp | gzip > backup.sql.gz

# Restore:
gunzip -c backup.sql.gz | docker exec -i mysql mysql -u myapp -pmypassword myapp
```

---

## MariaDB: MySQL Without Oracle

[MariaDB](https://mariadb.org) was created in 2009 by Michael "Monty" Widenius — the original creator of MySQL — after Oracle acquired Sun Microsystems (and MySQL with it). MariaDB is a drop-in MySQL replacement with cleaner governance (MariaDB Foundation, non-profit) and some technical improvements.

### Why MariaDB Instead of MySQL

**No Oracle dependency.** MariaDB Foundation governs the project with community input — similar to PostgreSQL's governance model.

**Galera Cluster.** MariaDB includes native Galera synchronous multi-master replication — all nodes accept writes, writes replicate synchronously to all nodes. MySQL's InnoDB Cluster achieves similar results but with more complexity.

```yaml
# MariaDB Galera cluster (3-node):
services:
  mariadb-1:
    image: mariadb:11
    environment:
      MARIADB_ROOT_PASSWORD: secret
      MARIADB_GALERA_CLUSTER_NAME: my-cluster
      MARIADB_GALERA_NODE_ADDRESS: mariadb-1
      MARIADB_GALERA_CLUSTER_ADDRESS: gcomm://mariadb-1,mariadb-2,mariadb-3
```

**Drop-in MySQL replacement.** The vast majority of MySQL applications work unmodified with MariaDB — same port (3306), same client protocol, same SQL dialect.

**Better JSON in newer versions.** MariaDB 11.2+ improved JSON handling significantly.

### Docker Setup

```yaml
services:
  mariadb:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: myapp
      MARIADB_USER: myapp
      MARIADB_PASSWORD: "${MARIADB_PASSWORD}"
      MARIADB_ROOT_PASSWORD: "${MARIADB_ROOT_PASSWORD}"
    volumes:
      - mariadb_data:/var/lib/mysql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
```

### Common Operations

```bash
# Connect (uses mysql client — identical to MySQL):
docker exec -it mariadb mariadb -u myapp -p myapp

# Backup:
docker exec mariadb mysqldump -u myapp -pmypassword myapp | gzip > backup.sql.gz
```

---

## Performance Comparison

All three databases handle typical self-hosted workloads comfortably. Performance differences matter at scale (100K+ queries/second, billions of rows).

**For typical self-hosted apps (< 10K req/s):**

| Scenario | Fastest |
|----------|---------|
| Simple reads (SELECT by primary key) | MySQL ≈ MariaDB ≈ PostgreSQL |
| Complex joins and aggregations | PostgreSQL |
| JSON/document storage and queries | PostgreSQL (JSONB) |
| Full-text search | PostgreSQL (tsvector) ≈ MySQL |
| Write-heavy workloads | MySQL ≈ MariaDB ≈ PostgreSQL |
| Geospatial queries | PostgreSQL + PostGIS |

For an app with <1M rows and <1K concurrent connections, any of the three performs excellently.

---

## Migration Between Databases

### MySQL/MariaDB → PostgreSQL

```bash
# Use pgloader for MySQL→PostgreSQL migration:
pgloader mysql://user:pass@localhost/source_db \
  postgresql://user:pass@localhost/target_db
```

pgloader handles type conversions, constraints, and indexes automatically.

### PostgreSQL → MySQL

Harder — PostgreSQL's advanced types (JSONB, arrays, custom types) don't map directly to MySQL. Requires manual schema adaptation.

---

## Framework Defaults

| Framework | Default Database |
|-----------|-----------------|
| Django | PostgreSQL (recommended) |
| Ruby on Rails | PostgreSQL (recommended) |
| Laravel | MySQL (but PostgreSQL supported) |
| Next.js / Prisma | PostgreSQL (recommended) |
| WordPress | MySQL / MariaDB |
| Drupal | MySQL / MariaDB / PostgreSQL |
| Supabase | PostgreSQL |
| PocketBase | SQLite → PostgreSQL |

The trend in modern full-stack frameworks (Next.js, Remix, SvelteKit) is strongly toward PostgreSQL.

---

## Decision Guide

```
Choose PostgreSQL if:
  → New project with no legacy constraints
  → You use Prisma, Drizzle, SQLAlchemy, or Active Record (all support it)
  → You need JSONB, arrays, or advanced SQL features
  → You want PostGIS for geospatial data
  → You want pgvector for AI/vector similarity search
  → True open source governance matters

Choose MySQL 8.x if:
  → Running WordPress, Drupal, or Joomla
  → Legacy PHP codebase with MySQL-specific queries
  → Existing MySQL infrastructure with no reason to migrate
  → Using managed services (PlanetScale, AWS RDS MySQL)

Choose MariaDB if:
  → You want MySQL compatibility without Oracle dependency
  → You need Galera synchronous multi-master replication
  → Your Linux distro ships MariaDB by default
  → Commercial product where MySQL's GPL dual-license is a concern
```

---

*See all open source database tools at [OSSAlt.com/categories/databases](https://ossalt.com).*


## Database Choice Is Mostly About Failure Handling

Database comparisons often drift into benchmark theater, but production teams care more about the shape of failure than raw throughput. How obvious is replication lag? How painful are schema changes? Which engine has the extensions, backup tooling, and operational talent your team already understands? These questions usually dominate total ownership cost. A slightly slower database with simpler recovery and stronger team familiarity is often the better self-hosting choice than a theoretically faster engine that nobody can debug under pressure.

That operational lens also pulls in adjacent tooling. [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026) is relevant because backup validation matters more than backup configuration. [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026) belongs here because query latency, disk pressure, and replication health need visibility before users notice a problem. [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026) can be part of the story when the database is deployed alongside applications and environment management needs to stay consistent.

## What to Standardize Before Production

Before production, standardize a few non-negotiables: naming conventions, migration ownership, restore testing cadence, and one preferred backup format. Decide who can run destructive operations and how those actions are reviewed. The point is not bureaucracy. It is to reduce improvisation when the system is under stress. Teams that document these basics early tend to have calmer incidents and faster onboarding.

That is the most useful perspective for a database article. Readers do not just need to know which engine is fastest on a benchmark. They need to know which one fits the operational maturity of the organization that will run it.


## Related Reading

- [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026)
- [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026)
- [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026)


## Database Choice Is Mostly About Failure Handling

Database comparisons often drift into benchmark theater, but production teams care more about the shape of failure than raw throughput. How obvious is replication lag? How painful are schema changes? Which engine has the extensions, backup tooling, and operational talent your team already understands? These questions usually dominate total ownership cost. A slightly slower database with simpler recovery and stronger team familiarity is often the better self-hosting choice than a theoretically faster engine that nobody can debug under pressure.

That operational lens also pulls in adjacent tooling. [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026) is relevant because backup validation matters more than backup configuration. [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026) belongs here because query latency, disk pressure, and replication health need visibility before users notice a problem. [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026) can be part of the story when the database is deployed alongside applications and environment management needs to stay consistent.

## What to Standardize Before Production

Before production, standardize a few non-negotiables: naming conventions, migration ownership, restore testing cadence, and one preferred backup format. Decide who can run destructive operations and how those actions are reviewed. The point is not bureaucracy. It is to reduce improvisation when the system is under stress. Teams that document these basics early tend to have calmer incidents and faster onboarding.

That is the most useful perspective for a database article. Readers do not just need to know which engine is fastest on a benchmark. They need to know which one fits the operational maturity of the organization that will run it.


## Related Reading

- [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026)
- [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026)
- [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026)

## Restore Drill Notes

Restore drills are the only credible proof that a backup policy works. Teams should test a representative restore on a schedule, not wait for a real outage to discover format or permissions problems.


## Database Choice Is Mostly About Failure Handling

Database comparisons often drift into benchmark theater, but production teams care more about the shape of failure than raw throughput. How obvious is replication lag? How painful are schema changes? Which engine has the extensions, backup tooling, and operational talent your team already understands? These questions usually dominate total ownership cost. A slightly slower database with simpler recovery and stronger team familiarity is often the better self-hosting choice than a theoretically faster engine that nobody can debug under pressure.

That operational lens also pulls in adjacent tooling. [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026) is relevant because backup validation matters more than backup configuration. [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026) belongs here because query latency, disk pressure, and replication health need visibility before users notice a problem. [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026) can be part of the story when the database is deployed alongside applications and environment management needs to stay consistent.

## What to Standardize Before Production

Before production, standardize a few non-negotiables: naming conventions, migration ownership, restore testing cadence, and one preferred backup format. Decide who can run destructive operations and how those actions are reviewed. The point is not bureaucracy. It is to reduce improvisation when the system is under stress. Teams that document these basics early tend to have calmer incidents and faster onboarding.

That is the most useful perspective for a database article. Readers do not just need to know which engine is fastest on a benchmark. They need to know which one fits the operational maturity of the organization that will run it.


## Related Reading

- [Duplicati backup guide](/guides/how-to-self-host-duplicati-encrypted-cloud-backup-2026)
- [Prometheus and Grafana guide](/guides/how-to-self-host-prometheus-grafana-metrics-dashboards-2026)
- [Coolify guide](/guides/how-to-self-host-coolify-open-source-vercel-alternative-2026)
