diff --git a/docs/enterprise_edition/autodiscovery.md b/docs/enterprise_edition/autodiscovery.md
new file mode 100644
index 00000000..2220947b
--- /dev/null
+++ b/docs/enterprise_edition/autodiscovery.md
@@ -0,0 +1,113 @@
+---
+icon: material/database-eye
+---
+
+# Automatic database discovery
+
+When deployed in front of AWS Aurora databases, PgDog can automatically detect the cluster instances and configure them in `pgdog.toml`. This is useful when Aurora uses replica autoscaling, which can add or remove instances at any time.
+
+## How it works
+
+This feature is **disabled** by default. To enable it, add at least one Aurora host to `pgdog.toml` and enable autodiscovery:
+
+=== "pgdog.toml"
+ ```toml
+ [[databases]]
+ name = "postgres"
+ host = "any-instance.account-id.region.rds.amazonaws.com"
+
+ [autodiscovery]
+ enabled = true
+ ```
+=== "Helm chart"
+ ```yaml
+ databases:
+ - name: "postgres"
+ host: "any-instance.account-id.region.rds.amazonaws.com"
+ autodiscovery:
+ enabled: true
+ ```
+
+When enabled, PgDog connects to the first available host for each database in the configuration and runs the [`aurora_replica_status()`](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/aurora_replica_status.html) function to get the list of instances in the cluster.
+
+PgDog then replaces all entries in `pgdog.toml` with the discovered hosts and reloads its configuration automatically.
+
+### Autoscaling events
+
+To keep the list of databases in sync with Aurora autoscaling events, PgDog periodically queries the first available host in its config and runs the replica discovery function again.
+
+If the list of databases has changed, PgDog updates its config and reloads it. The interval for this check is configurable:
+
+=== "pgdog.toml"
+ ```toml
+ [autodiscovery]
+ enabled = true
+ check_interval = 5_000
+ ```
+
+=== "Helm chart"
+ ```yaml
+ autodiscovery:
+ enabled: true
+ checkInterval: 5000
+ ```
+
+### Filtering databases
+
+When enabled, autodiscovery runs for all databases in `pgdog.toml` by default. If some of your databases are not running on Aurora, or you want autodiscovery on some databases but not others, you can configure which ones it applies to:
+
+=== "pgdog.toml"
+ ```toml
+ [[databases]]
+ name = "postgres"
+ host = "any-instance.account-id.region.rds.amazonaws.com"
+
+ [[databases]]
+ name = "staging"
+ host = "not-aurora.account-id.region.rds.amazonaws.com"
+
+ [autodiscovery]
+ enabled = true
+
+ [[autodiscovery.databases]]
+ name = "postgres"
+ ```
+=== "Helm chart"
+ ```yaml
+ databases:
+ - name: "postgres"
+ host: "any-instance.account-id.region.rds.amazonaws.com"
+ - name: "staging"
+ host: "not-aurora.account-id.region.rds.amazonaws.com"
+ autodiscovery:
+ enabled: true
+ databases:
+ - name: "postgres"
+ ```
+
+In this example, only the `postgres` database will have autodiscovery enabled.
+
+!!! note "Configuring databases"
+ If you specify the `[[autodiscovery.databases]]` config, any database _not_ listed there will
+ not have autodiscovery enabled.
+
+### Replicas only
+
+If you're not using the read/write separation (single endpoint) feature of the [load balancer](../features/load-balancer/index.md#single-endpoint), you may want to configure read and write connection pools separately.
+
+To exclude the writer instance from host discovery for a read-only connection pool, set `replicas_only` in the autodiscovery database settings:
+
+=== "pgdog.toml"
+ ```toml
+ [[autodiscovery.databases]]
+ name = "prod_readonly"
+ replicas_only = true
+ ```
+
+=== "Helm chart"
+ ```yaml
+ autodiscovery:
+ databases:
+ - name: "postgres"
+ replicasOnly: true
+ ```
diff --git a/docs/features/authentication.md b/docs/features/authentication.md
index b2a82c79..349f1b86 100644
--- a/docs/features/authentication.md
+++ b/docs/features/authentication.md
@@ -5,12 +5,12 @@ icon: material/login
PostgreSQL servers support many authentication mechanisms. PgDog supports a subset of those, with the aim to support all of them over time. Since PostgreSQL 14, `scram-sha-256` is widely used to encrypt passwords and PgDog supports this algorithm for both client and server connections.
-Authentication is **enabled** by default. Applications connecting to PgDog must provide a username and password which is configured in [`users.toml`](../configuration/users.toml/users.md).
+Authentication is **enabled** by default. Applications connecting to PgDog must provide a username and password, configured in [`users.toml`](../configuration/users.toml/users.md).
## Supported methods
-PgDog implements a subset of authentication methods supported by Postgres. We're continuously working on adding more. The following table summarizes which algorithms are supported for client and server connections.
+PgDog implements a subset of authentication methods supported by Postgres and some others commonly used in the industry. The following table summarizes the current level of support for client and server connection authentication methods:
| Authentication method | Client connections | Server connections |
|-|-|-|
@@ -19,11 +19,16 @@ PgDog implements a subset of authentication methods supported by Postgres. We're
| `md5` | :material-check-circle-outline: | :material-check-circle-outline: |
| `plain` | :material-check-circle-outline: | :material-check-circle-outline: |
| `trust` | :material-check-circle-outline: | :material-check-circle-outline: |
-| [AWS IAM](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) | No | :material-check-circle-outline: |
+| [AWS RDS IAM](#rds-iam-authentication) | No | :material-check-circle-outline: |
+| [Azure Workload Identity](#azure-workload-identity-authentication) | No | :material-check-circle-outline: |
-### Client authentication
+!!! note "Contributions"
+ PgDog is an open source project. If you'd like to add an authentication method we don't currently support,
+ please [open an issue](https://github.com/pgdogdev/pgdog/issues) to discuss.
-By default, client connections will use `scram-sha-256` for password encryption during the authentication handshake. This method is secure and recommended in production. PgDog does support using others, and you can change it with configuration:
+## Client authentication
+
+By default, client connections will use password authentication encrypted with `scram-sha-256`. This method is secure and recommended for production usage. PgDog supports using other methods, e.g., `md5` and `plain`, which you can change with configuration:
=== "pgdog.toml"
```toml
@@ -35,38 +40,49 @@ By default, client connections will use `scram-sha-256` for password encryption
authType: scram
```
-Available options currently are:
+The following password authentication algorithms are available for client connections:
+
+| Configuration | Description |
+|-|-|
+| `scram` | The most secure password encryption, used by default in PostgreSQL. |
+| `md5` | Deprecated and insecure, but an order of magnitude faster than SCRAM. Still commonly used with connection poolers (e.g., pgbouncer). |
+| `plain` | No encryption is used and the password is sent as-is. Commonly used with TLS-encrypted connections. |
+| `trust` | Disables password authentication and allows all users to login. |
-- `scram` (SCRAM-SHA-256)
-- `md5`
-- `plain`
-- `trust` (No authentication)
-!!! note "SCRAM overhead"
- The SCRAM-SHA-256 algorithm is computationally expensive and will use a considerable amount of CPU time. This is by design, in order to make it difficult to brute-force. However, if your application is frequently creating new connections to the database, like serverless apps running on Vercel or Cloudflare workers, this could also have a latency impact.
+### SCRAM performance
+
+The SCRAM-SHA-256 algorithm is computationally expensive and will use a considerable amount of CPU time. This is by design, since it makes passwords difficult to brute-force. However, if your application is frequently creating new connections to the database, like serverless apps running on Vercel, Cloudflare workers, etc., this could also have a latency impact.
- If your application is affected by this, consider enabling [TLS](tls.md) and `plain` authentication method instead. Modern CPUs implement TLS algorithms in hardware which makes them pretty efficient and fast.
+If your application is affected by this, consider enabling [TLS](tls.md) and using the `plain` authentication method instead. Modern CPUs implement TLS algorithms in hardware which makes them efficient and fast.
+
+## Server authentication
-### Server authentication
+The server authentication method is controlled by PostgreSQL. PgDog will use whatever method Postgres requests during connection creation, which is configurable in the [`pg_hba.conf`](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html) file on the server.
-Server authentication method is controlled by PostgreSQL. PgDog will use whatever method Postgres requests during connection creation, which is configurable in [`pg_hba.conf`](https://www.postgresql.org/docs/current/auth-pg-hba-conf.html).
+PgDog currently supports four authentication methods for server connections:
-PgDog currently supports three authentication methods for server connections:
+| Method | Description |
+|-|-|
+| Password authentication | PgDog sends the password using one of the supported [password encryption](#client-authentication) algorithms. |
+| [AWS RDS IAM](#rds-iam-authentication) | PgDog requests a temporary password from AWS IAM and uses that for password authentication instead. |
+| [Azure Workload Identity](#azure-workload-identity-authentication) | Same method as AWS RDS IAM, except supported on Azure. |
+| Hashicorp Vault | PgDog connects to a configured instance of Hashicorp Vault and uses the provided username and password. |
-1. Password authentication, using any of the [client authentication](#client-authentication) methods
-2. AWS RDS IAM authentication
-3. Azure Workload Identity authentication
+### RDS IAM authentication
+PgDog supports using temporary credentials from [AWS IAM](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html) and using those to connect to RDS PostgreSQL (and Aurora) instances.
-#### RDS IAM authentication
+Under the hood, PgDog is using the [AWS RDS SDK](https://docs.rs/aws-sdk-rds/latest/aws_sdk_rds/) to fetch credentials at runtime. The SDK can retrieve them from the environment and from the EC2 IAM API. For example, if you're deploying PgDog in [Kubernetes](../installation.md#kubernetes), you just need to assign its container an IAM role with the right permissions.
-PgDog supports authenticating to RDS PostgreSQL (and Aurora) databases using [IAM](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.IAMDBAuth.html). This is configurable on a user-per-user basis, for example:
+To use RDS IAM authentication, configure it on each user in [`users.toml`](../configuration/users.toml/users.md):
=== "users.toml"
```toml
[[users]]
name = "pgdog"
database = "prod"
+ password = "hunter2"
server_auth = "rds_iam"
```
=== "Helm chart"
@@ -74,18 +90,22 @@ PgDog supports authenticating to RDS PostgreSQL (and Aurora) databases using [IA
users:
- name: pgdog
database: prod
+ password: hunter2
serverAuth: rds_iam
```
-#### Azure Workload Identity authentication
+### Azure Workload Identity authentication
-Similar to RDS IAM, PgDog can authenticate to PostgreSQL running in Azure using the built-in Workload Identity provider. This is configurable on a user-per-user basis, for example:
+PgDog supports using temporary credentials provided by Azure Workload Identity to connect to PostgreSQL running on Azure. This uses the [Azure SDK](https://github.com/Azure/azure-sdk-for-rust) and supports fetching credentials from the environment.
+
+To use Workload Identity authentication, configure it on each user in [`users.toml`](../configuration/users.toml/users.md):
=== "users.toml"
```toml
[[users]]
name = "pgdog"
database = "prod"
+ password = "hunter2"
server_auth = "azure_workload_identity"
```
=== "Helm chart"
@@ -93,12 +113,19 @@ Similar to RDS IAM, PgDog can authenticate to PostgreSQL running in Azure using
users:
- name: pgdog
database: prod
+ password: hunter2
serverAuth: azure_workload_identity
```
-## Add users
-[`users.toml`](../configuration/users.toml/users.md) follows a simple TOML list structure. To add users, simply add another `[[users]]` section, e.g.:
+### Hashicorp Vault
+
+!!! info "TODO: Documentation"
+ Support for Hashicorp Vault authentication has been added in [v0.1.46](https://github.com/pgdogdev/pgdog/releases/tag/v0.1.46). It has not been documented or thoroughly tested yet.
+
+## Configuring users
+
+The [`users.toml`](../configuration/users.toml/users.md) configuration file follows a TOML list structure. To allow a user to connect to PgDog, add a `[[users]]` section with the user name, password and a corresponding database name (located in [`pgdog.toml`](../configuration/pgdog.toml/databases.md)) to `users.toml`, for example:
=== "users.toml"
```toml
@@ -115,11 +142,29 @@ Similar to RDS IAM, PgDog can authenticate to PostgreSQL running in Azure using
password: hunter2
```
-PgDog will expect clients connecting as `pgdog` to provide the password `hunter2` (hashed with `scram-sha-256` by default), and will use the same username and password to connect to PostgreSQL.
+The `database` parameter must match the name of one of the databases configured in [`pgdog.toml`](../configuration/pgdog.toml/databases.md). For example:
-### Override server credentials
+=== "users.toml"
+ ```toml
+ [[users]]
+ name = "alice"
+ database = "prod"
+ password = "hunter2"
+ ```
+=== "pgdog.toml"
+ ```toml
+ [[databases]]
+ name = "prod"
+ host = "10.0.0.1"
+ ```
+
+The `database` parameter in the `[[users]]` entry ("prod") matches the `[[databases]]` entry with the same `name` ("prod"). If you add a user with a `database` name not specified in `pgdog.toml`, that user entry will be ignored by PgDog at runtime and that user will not be able to connect.
+
+The same username, database name and password will also be used by PgDog to connect to PostgreSQL. This makes configuration simpler since the Postgres connection options used by applications don't have to change when PgDog is deployed for the first time.
+
+### Overriding server credentials
-You can override the user and/or password PgDog uses to connect to Postgres by specifying `server_user` and `server_password` in the same configuration:
+If you want to use a different username or password for PgDog to connect to Postgres, you can override them by specifying the `server_user` and `server_password` parameters in the same user configuration entry:
=== "users.toml"
```toml
@@ -140,13 +185,13 @@ You can override the user and/or password PgDog uses to connect to Postgres by s
serverPassword: opensesame
```
-This allows you to separate client and server credentials. In case your clients accidentally leak theirs, you only need to rotate them in the PgDog configuration, without having to take downtime to change passwords in PostgreSQL.
+This allows you to separate client and server authentication credentials. In case your applications accidentally leak their credentials (e.g., by committing them to git), you only need to rotate them in the PgDog configuration, without having to take downtime to change passwords in PostgreSQL.
## Passthrough authentication
-Passthrough authentication is a feature where instead of storing passwords in `users.toml`, PgDog attempts to connect to Postgres using the credentials provided by the client. Passthrough authentication simplifies PgDog deployments by using a single source of truth for authentication and doesn't require passwords to be stored outside the database.
+With passthrough authentication, instead of storing passwords in `users.toml`, PgDog connects to PostgreSQL using the credentials provided by the client. Passthrough authentication simplifies PgDog deployments by using a single source of truth for user credentials and doesn't require passwords to be stored outside the database or the application.
-Passthrough authentication is disabled by default and can be enabled with configuration:
+Passthrough authentication is **disabled** by default and can be enabled with configuration:
=== "pgdog.toml"
```toml
@@ -158,15 +203,17 @@ Passthrough authentication is disabled by default and can be enabled with config
passthroughAuth: enabled
```
-This will require clients to send passwords in plain text. PgDog will create a connection pool for the database/user pair and the provided password. The database must exist in [`pgdog.toml`](../configuration/pgdog.toml/databases.md).
+Since PgDog doesn't store the server password anymore, using passthrough authentication will require clients to send passwords in plain text. Therefore, PgDog will automatically change the `auth_method` to `plain` and ignore the setting configured in `pgdog.toml`.
-The connection pool is dynamic and will be created the first time a client connects. When configuration is reloaded, the connection pool is removed. As long as `passthrough_auth` is enabled between config changes, clients won't be impacted, since connection pools will be recreated next time clients reconnect or execute a query.
+When a client connects to PgDog for the first time, it will create a connection pool for the database/user pair and the provided password. The database specified by the client must still exist in [`pgdog.toml`](../configuration/pgdog.toml/databases.md).
-### Security
+When configuration is reloaded, connection pools created with passthrough auth are temporarily removed and immediately re-created when a connected client executes a query. As long as `passthrough_auth` is enabled between configuration changes, clients will not be impacted.
-Sending plain text passwords over unencrypted connections isn't ideal, even if PgDog and Postgres are on the same local network. For this reason, `passthrough_auth = "enabled"` will only work if PgDog is configured to use [TLS encryption](tls.md).
+### Passthrough authentication security
-If you don't want to set up TLS (it has some impact on latency), you can override this behavior and send passwords via plain text (unsecured) connection:
+Sending passwords in plain text over unencrypted connections is not great, even if PgDog and Postgres are on the same local network. For this reason, `passthrough_auth = "enabled"` will only work if PgDog is configured to use [TLS encryption](tls.md).
+
+If you don't want to set up TLS (it has some impact on latency), you can override this behavior and send passwords via plain text and an unencrypted connection:
=== "pgdog.toml"
```toml
@@ -178,6 +225,60 @@ If you don't want to set up TLS (it has some impact on latency), you can overrid
passthroughAuth: enabled_plain
```
+### Configuring user options
+
+The most typical deployments of PgDog with passthrough authentication do not configure a `users.toml` at all. However, some user-specific options can only be configured in that file, for example, [server authentication](#server-authentication). To configure users with options and passthrough authentication, add them to `users.toml` without specifying a password:
+
+=== "users.toml"
+ ```toml
+ [[users]]
+ name = "alice"
+ pool_size = 10
+ server_auth = "rds_iam"
+ ```
+=== "Helm chart"
+ ```yaml
+ users:
+ - name: alice
+ poolSize: 10
+ serverAuth: rds_iam
+ ```
+
+Passthrough authentication must still be enabled in `pgdog.toml`. PgDog will use the password supplied by the client and create a connection pool dynamically when they connect for the first time.
+
+### Changing passwords
+
+Connection pools created dynamically with passthrough authentication will have a static password. If that password is changed inside the server (e.g., by running `ALTER USER [...] PASSWORD` command), PgDog will no longer be able to connect to the database. To change the password in PgDog without restarting the proxy, you can configure the passthrough auth passwords to be changeable:
+
+=== "pgdog.toml"
+ ```toml
+ [general]
+ passthrough_auth = "enabled_allow_change"
+ ```
+=== "Helm chart"
+ ```yaml
+ passthroughAuth: enabled_allow_change
+ ```
+
+When a client connects with a different password to what's currently stored in PgDog's memory, it will re-create the connection pool with the new password and re-connect to the server.
+
+!!! warning "Trusted clients only"
+ Allowing clients to change their passwords at runtime should never be used with PgDog deployments open to the Internet. This feature is built for convenience to allow almost zero-downtime password rotation in Postgres and, if used incorrectly, can open up the proxy to a denial-of-service attack.
+
+#### Plain text connections
+
+If passthrough authentication is used without [TLS](tls.md), set it to `"enabled_plain_allow_change"` instead:
+
+=== "pgdog.toml"
+ ```toml
+ [general]
+ passthrough_auth = "enabled_plain_allow_change"
+ ```
+=== "Helm chart"
+ ```yaml
+ passthroughAuth: enabled_plain_allow_change
+ ```
+
## Password security
-Since PgDog stores passwords in a separate configuration file, it's possible to encrypt it at rest without compromising the DevOps experience. For example, Kubernetes provides built-in [secrets management](https://kubernetes.io/docs/concepts/configuration/secret/) to manage this.
+Since PgDog stores passwords in a separate configuration file (i.e., `users.toml`), it's possible to encrypt them at rest without compromising the DevOps experience. For example, Kubernetes provides built-in [secrets management](https://kubernetes.io/docs/concepts/configuration/secret/) to manage this, and our [Helm chart](../installation.md#kubernetes) automatically takes advantage of it.
diff --git a/docs/features/transaction-mode.md b/docs/features/transaction-mode.md
index 81d1e355..3ebf55c2 100644
--- a/docs/features/transaction-mode.md
+++ b/docs/features/transaction-mode.md
@@ -27,6 +27,7 @@ Transaction mode is **enabled** by default. This is controllable via configurati
[general]
pooler_mode = "transaction"
```
+
```yaml title="Helm chart"
poolerMode: transaction
```
@@ -37,6 +38,7 @@ Transaction mode is **enabled** by default. This is controllable via configurati
host = "127.0.0.1"
pooler_mode = "transaction"
```
+
```yaml title="Helm chart"
databases:
- name: prod
@@ -50,6 +52,7 @@ Transaction mode is **enabled** by default. This is controllable via configurati
database = "prod"
pooler_mode = "transaction"
```
+
```yaml title="Helm chart"
users:
- name: alice
@@ -145,6 +148,7 @@ To use statement mode, you can configure it globally or per user/database, for e
[general]
pooler_mode = "statement"
```
+
```yaml title="Helm chart"
poolerMode: statement
```
@@ -155,6 +159,7 @@ To use statement mode, you can configure it globally or per user/database, for e
host = "127.0.0.1"
pooler_mode = "statement"
```
+
```yaml title="Helm chart"
databases:
- name: prod
@@ -168,6 +173,7 @@ To use statement mode, you can configure it globally or per user/database, for e
database = "prod"
pooler_mode = "statement"
```
+
```yaml title="Helm chart"
users:
- name: alice
diff --git a/docs/images/logo-blue-64x64.png b/docs/images/logo-blue-64x64.png
new file mode 100644
index 00000000..37f74b30
Binary files /dev/null and b/docs/images/logo-blue-64x64.png differ
diff --git a/docs/images/resharding-intro.png b/docs/images/resharding-intro.png
new file mode 100644
index 00000000..576d79ef
Binary files /dev/null and b/docs/images/resharding-intro.png differ
diff --git a/docs/index.md b/docs/index.md
index 394ea0c8..29206691 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -4,30 +4,56 @@ icon: material/home
# Introduction to PgDog
-[PgDog](https://pgdog.dev) is a sharder, connection pooler and load balancer for PostgreSQL. Written in Rust, PgDog is fast, reliable and scales databases horizontally without requiring changes to application code.
+PgDog is a connection pooler, load balancer and database sharder for PostgreSQL. Written in Rust, PgDog is fast, reliable and scales Postgres databases without requiring changes to your application.
-## The problem
+## Getting started
-Unlike NoSQL databases, PostgreSQL can serve `INSERT`, `UPDATE`, and `DELETE` queries from only one machine. Once the capacity of that machine is exceeded, applications have to find new and creative ways to reduce their impact on the database, like batching requests or delaying workloads to run overnight.
+PgDog is an open source project. You can download its code from our [repository](https://github.com/pgdogdev/pgdog) on GitHub. If you're deploying PgDog to your cloud account (or on prem), you can either use the [compiled binaries](https://github.com/pgdogdev/pgdog/releases) we provide or build it from source.
-At the same time, database operators are faced with increasing operating costs, like behind schedule vacuums, table bloat and downtime. Incidents are frequent and engineers are more focused on not breaking the DB than building new features.
+Every commit in the `main` branch and weekly tagged releases have corresponding images in our [Docker](https://github.com/orgs/pgdogdev/packages/container/package/pgdog) repository, for example:
+
+```bash
+docker run ghcr.io/pgdogdev/pgdog:v0.1.46
+```
+
+## Why PgDog
+
+PostgreSQL is a process-based, single-primary database. As such, it has hard limits on how many clients can connect, how many queries a single server can execute, and how much data it can write at any given time.
+
+PgDog helps you work around these limits. It's a single binary, deployed between your application and the database, that speaks both the protocol applications use to talk to Postgres and the replication protocol Postgres uses internally. This lets PgDog do its job transparently, without changes to Postgres or the applications that query it.
+
+## Connection pooler
+
+Like PgBouncer or RDS Proxy, PgDog is a connection pooler: it multiplexes thousands of application connections over just a handful of Postgres server connections. This effectively removes the limit on how many clients a PostgreSQL database can serve at once.
+
+Unlike those proxies, PgDog handles features that usually force a pooler to pin or reset connections. It supports SET statements, LISTEN/NOTIFY, and advisory locks without breaking connection state, so your application keeps working as if it were talking to Postgres directly.
+
+PgDog is also multithreaded, so a single instance can serve many more clients while still relying on the same small number of Postgres connections.
+
+You can read more about how PgDog handles transactions [here](features/transaction-mode.md).
+
+## Load balancer
+
+If your database has read replicas, PgDog can distribute read queries across them using one of several built-in load balancing [algorithms](features/load-balancer/index.md). This lets you scale reads simply by adding replicas, with no application changes and no extra infrastructure like HAProxy or Patroni.
+
+You can read more about how PgDog load balances queries [here](features/load-balancer/index.md).
## Sharding PostgreSQL
-The solution to an overextended database is **sharding**: splitting all tables and indices equally between multiple machines. For example, if your primary database is 750 GB, splitting it into 3 shards will produce 3 databases of 250 GB each. As databases get smaller, vacuums start to catch up, indices fit into memory again, and queries run faster with reliable performance.
+Unlike NoSQL databases, PostgreSQL can serve INSERT, UPDATE, and DELETE queries from only one server. Once that server's capacity is exceeded, applications have to find new and creative ways to reduce their load on the database, such as batching requests or deferring workloads to run overnight.
-As shards store more data and grow, they can be split again, scaling PostgreSQL horizontally. Sharded databases can grow into petabytes (that's thousands of TB), while serving OLTP and OLAP use cases.
+At the same time, database operators face rising operating costs from vacuums that fall behind schedule, table bloat and downtime. Incidents become frequent, and engineers end up more focused on keeping the database from breaking than on building new features.
-## How PgDog works
+The solution to an overextended database is sharding: splitting all tables and indices equally between multiple machines. For example, if your primary database is 750 GB, splitting it into 3 shards will produce 3 databases of 250 GB each. As databases get smaller, vacuums start to catch up, indices fit into memory again, and queries run faster with reliable performance.
-PgDog operates on the application layer of the stack: it speaks PostgreSQL and understands not only the queries sent by applications but also the logical replication protocol used by the server. This allows it to automatically route queries while moving data between machines to create more capacity.
+As shards accumulate more data and grow, they can be split again, scaling PostgreSQL horizontally. Sharded databases can grow into petabytes (thousands of TB) while serving both OLTP and OLAP workloads.
-
+
-While PgDog focuses a lot on sharding PostgreSQL, it is also a load balancer and transaction pooler that can be used with simpler PostgreSQL deployments.
+PgDog operates on the application layer of the stack: it speaks PostgreSQL and understands not only the queries sent by applications but also the logical replication protocol used by the server. This allows it to automatically route queries while moving data between machines to create more capacity.
This documentation provides a detailed overview of all PgDog features, along with reference material for production operations.
diff --git a/docs/installation.md b/docs/installation.md
index 35c0dd37..ca7066f1 100644
--- a/docs/installation.md
+++ b/docs/installation.md
@@ -31,22 +31,23 @@ docker run ghcr.io/pgdogdev/pgdog:v0.1.44
### AWS ECS
!!! note "New feature"
- This is a new feature. Please report any issues you may encounter.
+ The ECS Terraform module is a new project. Please report any issues you may encounter. Community contributions are welcome.
- We recently added a [Terraform module](https://github.com/pgdogdev/pgdog-ecs-terraform) to deploy PgDog on AWS ECS. It works with the same Docker image as our Helm chart, so the experience should be familiar.
+We recently added a [Terraform module](https://github.com/pgdogdev/pgdog-ecs-terraform) to deploy PgDog on AWS ECS. It works with the same Docker image as our [Helm chart](#kubernetes), so the experience should be familiar.
## Pre-built binaries
-Each PgDog release (weekly, on Thursdays) contains pre-built binaries for Linux (arm64, amd64), Mac (aarch64, i.e. Apple Silicon), and Debian packages (`.deb`) for convenient installation on Debian/Ubuntu.
+Each PgDog release (every week, on Thursday) contains pre-built binaries for Linux (arm64, amd64), Mac (aarch64, i.e. Apple Silicon), and Debian packages (`.deb`) for convenient installation on Debian/Ubuntu.
-You can download pre-built binaries in [GitHub](https://github.com/pgdogdev/pgdog/releases).
+You can download all binaries from the [releases page](https://github.com/pgdogdev/pgdog/releases) in GitHub.
-#### glibc
-The Linux binaries are built on Ubuntu 24.04, and are linked against glibc version 2.39. To run them, your system needs glibc 2.39 or later.
+#### Linux binaries and glibc
-#### Mac OS
+The Linux binaries are built on Ubuntu 24.04 and are linked against glibc version 2.39. To run them, your system needs glibc 2.39 or later.
-The Mac OS binary is not signed. To run it locally, make sure to de-quarantine it:
+#### Mac OS security
+
+The Mac OS binary is not signed. To run it locally, make sure to de-quarantine it first, e.g.:
```bash
xattr -d com.apple.quarantine ./pgdog
@@ -54,11 +55,11 @@ xattr -d com.apple.quarantine ./pgdog
## From source
-PgDog can be easily compiled from source. For production deployments, a `Dockerfile` is provided in [GitHub](https://github.com/pgdogdev/pgdog/tree/main/Dockerfile). If you prefer to deploy on bare-metal or you're looking to run PgDog locally, you'll need to install a few dependencies.
+PgDog can be easily compiled from source. For production deployments, a [`Dockerfile`](https://github.com/pgdogdev/pgdog/tree/main/Dockerfile) is provided in our code repository. If you prefer to deploy on bare-metal or you are looking to run PgDog locally, you will need to install a few dependencies.
### Dependencies
-Parts of PgDog depend on C/C++ libraries, which are compiled from source. Make sure to have a working version of a C/C++ compiler installed.
+Parts of PgDog depend on C/C++ libraries, which are compiled from source. Make sure to have a working version of a C/C++ compiler installed before building from source:
=== "macOS"
Install [Xcode](https://developer.apple.com/xcode/) from the App Store and CMake & Clang from Homebrew:
@@ -102,7 +103,7 @@ git clone https://github.com/pgdogdev/pgdog && \
cd pgdog
```
-To make sure you get all performance benefits, PgDog should be compiled in release mode:
+To make sure you get all performance benefits, PgDog should be compiled in release mode with all optimizations:
```bash
cargo build --release
@@ -110,7 +111,7 @@ cargo build --release
### Launch PgDog
-You can start PgDog by running the binary directly, located in `target/release/pgdog`, or with Cargo:
+You can start PgDog by running the binary directly, which is located in `target/release/pgdog`, or by running it with Cargo:
```bash
cargo run --release
@@ -122,13 +123,12 @@ PgDog is configured via two files:
| Configuration file | Description |
|-|-|
-| [pgdog.toml](configuration/index.md) | Contains general settings and information about PostgreSQL servers. |
-| [users.toml](configuration/users.toml/users.md) | Contains users and passwords that are allowed to connect to PgDog. |
+| [pgdog.toml](configuration/index.md) | General settings and information about PostgreSQL servers. |
+| [users.toml](configuration/users.toml/users.md) | Usernames and passwords that are allowed to connect to PgDog. |
-Users are configured separately to allow them to be encrypted at rest in environments that support it, like in Kubernetes or with the AWS Secrets Manager.
+Users are configured separately, which allows them to be encrypted at rest in environments that support it, like in Kubernetes or with the AWS Secrets Manager.
-Both config files should be placed in the current working directory (`$PWD`) for PgDog to detect them. Alternatively,
-you can pass their paths at startup as arguments:
+If the configuration files are placed in the current working directory (`$PWD`), PgDog will detect them automatically. Alternatively, you can pass their paths at startup as arguments:
```bash
pgdog \
@@ -155,7 +155,7 @@ Most configuration options have sensible defaults. This makes single-database co
#### [users.toml](configuration/users.toml/users.md)
-This config file contains a mapping between databases, users, and passwords. Unless you configured [passthrough authentication](features/authentication.md#passthrough-authentication), users not specified in this file won't be able to connect:
+This config file contains a mapping between databases, users, and passwords. Unless you configured [passthrough authentication](features/authentication.md#passthrough-authentication), users not specified in this file will not be able to connect:
=== "users.toml"
```toml
@@ -173,7 +173,6 @@ This config file contains a mapping between databases, users, and passwords. Unl
```
!!! note "Configuring users"
-
PgDog creates connection pools for each user/database pair. If no users are configured in `users.toml`, all connection pools will be disabled and PgDog won't connect to the database(s).
## Next steps
diff --git a/docs/style.css b/docs/style.css
index 599958f3..708828ac 100644
--- a/docs/style.css
+++ b/docs/style.css
@@ -1,19 +1,381 @@
-/* ===== Custom Properties ===== */
+/* ==========================================================================
+ PgDog Docs Theme — ported onto MkDocs Material
+ Source design: "PgDog Docs Theme" (Claude Design)
+ A clean, modern docs look: PgDog brand blue, tight bold headings, subtle
+ borders, soft-tinted admonitions and rounded code blocks — layered on top
+ of Material so content tabs, card grids, search and instant-nav keep working.
+ ========================================================================== */
+
+/* ===== Shared tokens ===== */
:root {
- --card-border-radius: 8px;
- --card-padding: 1.2rem;
+ --pg-radius: 10px;
+ --pg-radius-lg: 12px;
--transition-speed: 0.2s;
+ --card-border-radius: 10px;
+ --card-padding: 1.2rem;
+}
+
+/* --------------------------------------------------------------------------
+ Light scheme → bind PgDog tokens onto Material's palette variables
+ -------------------------------------------------------------------------- */
+[data-md-color-scheme="default"] {
+ --pg-bg: #ffffff;
+ --pg-bg-subtle: #f7f8fa;
+ --pg-bg-code: #eceef2;
+ --pg-text: #1b1c21;
+ --pg-muted: #5c616b;
+ --pg-faint: #9aa0ab;
+ --pg-border: #ebecef;
+ --pg-border-strong: #e0e2e6;
+ --pg-accent: #3f6fe8;
+ --pg-accent-soft: rgba(63, 111, 232, 0.10);
+ --pg-hover: rgba(16, 17, 20, 0.045);
+ --pg-amber: #b5791b; --pg-amber-bg: rgba(217, 149, 40, 0.11); --pg-amber-bd: rgba(217, 149, 40, 0.40);
+ --pg-green: #1f8a5b; --pg-green-bg: rgba(31, 138, 91, 0.10); --pg-green-bd: rgba(31, 138, 91, 0.35);
+ --pg-red: #c0392b; --pg-red-bg: rgba(192, 57, 43, 0.09); --pg-red-bd: rgba(192, 57, 43, 0.35);
+
+ /* Material core palette */
+ --md-default-bg-color: var(--pg-bg);
+ --md-default-fg-color: var(--pg-text);
+ --md-default-fg-color--light: var(--pg-muted);
+ --md-default-fg-color--lighter: var(--pg-faint);
+ --md-default-fg-color--lightest: var(--pg-border);
+ --md-primary-fg-color: var(--pg-accent);
+ --md-primary-fg-color--light: var(--pg-accent);
+ --md-primary-fg-color--dark: var(--pg-accent);
+ --md-primary-bg-color: #ffffff;
+ --md-primary-bg-color--light: rgba(255, 255, 255, 0.7);
+ --md-accent-fg-color: var(--pg-accent);
+ --md-accent-fg-color--transparent: var(--pg-accent-soft);
+ --md-typeset-a-color: var(--pg-accent);
+ --md-code-bg-color: var(--pg-bg-code);
+ --md-code-fg-color: var(--pg-text);
+
+ /* Code syntax highlighting — restrained design palette */
+ --md-code-hl-keyword-color: #3f6fe8;
+ --md-code-hl-string-color: #2c8a60;
+ --md-code-hl-number-color: #b5791b;
+ --md-code-hl-comment-color: #6e7480;
+ --md-code-hl-constant-color: #b5791b;
+ --md-code-hl-name-color: var(--pg-text);
+ --md-code-hl-function-color: var(--pg-text);
+ --md-code-hl-operator-color: var(--pg-muted);
+ --md-code-hl-punctuation-color: var(--pg-muted);
+ --md-code-hl-variable-color: var(--pg-text);
+ --md-code-hl-special-color: #c0392b;
+ --md-code-hl-generic-color: var(--pg-faint);
+}
+
+/* --------------------------------------------------------------------------
+ Dark scheme (slate) → PgDog blue accent on near-black
+ -------------------------------------------------------------------------- */
+[data-md-color-scheme="slate"] {
+ --pg-bg: #111318;
+ --pg-bg-subtle: #14161b;
+ --pg-bg-code: #16181e;
+ --pg-text: #e9eaee;
+ --pg-muted: #979ca6;
+ --pg-faint: #666b75;
+ --pg-border: #22242b;
+ --pg-border-strong: #2c2f37;
+ --pg-accent: #7aa2ff;
+ --pg-accent-soft: rgba(122, 162, 255, 0.16);
+ --pg-hover: rgba(255, 255, 255, 0.05);
+ --pg-amber: #e0b463; --pg-amber-bg: rgba(224, 180, 99, 0.10); --pg-amber-bd: rgba(224, 180, 99, 0.32);
+ --pg-green: #4fcf94; --pg-green-bg: rgba(79, 207, 148, 0.10); --pg-green-bd: rgba(79, 207, 148, 0.30);
+ --pg-red: #f0857a; --pg-red-bg: rgba(240, 133, 122, 0.10); --pg-red-bd: rgba(240, 133, 122, 0.30);
+
+ --md-default-bg-color: var(--pg-bg);
+ --md-default-fg-color: var(--pg-text);
+ --md-default-fg-color--light: var(--pg-muted);
+ --md-default-fg-color--lighter: var(--pg-faint);
+ --md-default-fg-color--lightest: var(--pg-border);
+ --md-primary-fg-color: var(--pg-accent);
+ --md-primary-fg-color--light: var(--pg-accent);
+ --md-primary-fg-color--dark: var(--pg-accent);
+ --md-primary-bg-color: var(--pg-text);
+ --md-primary-bg-color--light: var(--pg-muted);
+ --md-accent-fg-color: var(--pg-accent);
+ --md-accent-fg-color--transparent: var(--pg-accent-soft);
+ --md-typeset-a-color: var(--pg-accent);
+ --md-code-bg-color: var(--pg-bg-code);
+ --md-code-fg-color: var(--pg-text);
+
+ --md-code-hl-keyword-color: #7aa2ff;
+ --md-code-hl-string-color: #5fcf94;
+ --md-code-hl-number-color: #e0b463;
+ --md-code-hl-comment-color: #8a8f99;
+ --md-code-hl-constant-color: #e0b463;
+ --md-code-hl-name-color: var(--pg-text);
+ --md-code-hl-function-color: var(--pg-text);
+ --md-code-hl-operator-color: var(--pg-muted);
+ --md-code-hl-punctuation-color: var(--pg-muted);
+ --md-code-hl-variable-color: var(--pg-text);
+ --md-code-hl-special-color: #f0857a;
+ --md-code-hl-generic-color: var(--pg-faint);
}
/* ===== Header ===== */
+/* Flat header: page background + a hairline border, like the design. */
.md-header {
- --md-primary-fg-color: rgb(7, 81, 207);
+ background-color: var(--pg-bg);
+ color: var(--pg-text);
+ border-bottom: 1px solid var(--pg-border);
+ box-shadow: none;
+ -webkit-backdrop-filter: blur(8px);
backdrop-filter: blur(8px);
}
+.md-header[data-md-state="shadow"],
+.md-header--shadow {
+ box-shadow: none;
+}
+.md-header__title {
+ color: var(--pg-text);
+ font-weight: 700;
+ letter-spacing: -0.3px;
+}
+/* Sit the title right next to the logo. Material's margin lives on a
+ [dir=ltr]-scoped rule, so match its specificity; also trim the logo button's
+ right padding/margin. */
+.md-header .md-header__title {
+ margin-left: 0.3rem;
+}
+.md-header .md-header__button.md-logo {
+ margin-right: 0;
+ padding-right: 0;
+}
+/* The logo PNG is white (made for the original dark header). On the light-mode
+ top bar that makes it invisible, so swap in a blue-recolored copy. */
+[data-md-color-scheme="default"] .md-header__button.md-logo img {
+ content: url("/images/logo-blue-64x64.png");
+}
+.md-header__button.md-icon,
+.md-header__source {
+ color: var(--pg-muted);
+}
+/* Light mode: darken the GitHub icon, repo name and stats (they inherit a
+ mid-grey and the facts are dimmed to 75% by default). */
+[data-md-color-scheme="default"] .md-header__source {
+ color: var(--pg-text);
+}
+[data-md-color-scheme="default"] .md-source__facts {
+ opacity: 0.85;
+}
+/* Dark mode: brighten the GitHub icon, repo name and stats. */
+[data-md-color-scheme="slate"] .md-header__source {
+ color: var(--pg-text);
+}
+[data-md-color-scheme="slate"] .md-source__facts {
+ opacity: 0.85;
+}
+.md-header__button.md-icon:hover {
+ color: var(--pg-text);
+ opacity: 1;
+}
+
+/* Search box in the header — subtle pill, like the design's search button. */
+.md-search__form {
+ background-color: var(--pg-bg-subtle);
+ border: 1px solid var(--pg-border);
+ border-radius: 10px;
+ box-shadow: none;
+}
+.md-search__form:hover {
+ background-color: var(--pg-bg-subtle);
+ border-color: var(--pg-border-strong);
+}
+/* Dark mode: lift the search box off the near-black header so it's prominent. */
+[data-md-color-scheme="slate"] .md-search__form {
+ background-color: #1c2029;
+ border-color: #353a45;
+}
+[data-md-color-scheme="slate"] .md-search__form:hover,
+[data-md-color-scheme="slate"] [data-md-toggle="search"]:checked ~ .md-header .md-search__form {
+ background-color: #222632;
+ border-color: var(--pg-accent);
+}
+[data-md-toggle="search"]:checked ~ .md-header .md-search__form {
+ border-color: var(--pg-border-strong);
+}
+.md-search__input {
+ color: var(--pg-text);
+}
+.md-search__input::placeholder {
+ color: var(--pg-muted);
+}
+.md-search__icon {
+ color: var(--pg-faint);
+}
+/* Light mode: the magnifier defaults to white (header context) — make it dark. */
+[data-md-color-scheme="default"] .md-search__icon {
+ color: var(--pg-text);
+}
+
+/* ===== Typography ===== */
+.md-typeset {
+ font-size: 0.8rem;
+ line-height: 1.75;
+ color: var(--pg-text);
+}
+.md-typeset h1 {
+ font-size: 1.9rem;
+ font-weight: 800;
+ letter-spacing: -0.03em;
+ line-height: 1.14;
+ color: var(--pg-text);
+ margin: 0 0 0.8rem;
+}
+.md-typeset h2 {
+ font-size: 1.25rem;
+ font-weight: 700;
+ letter-spacing: -0.02em;
+ margin: 2.2rem 0 0.7rem;
+}
+.md-typeset h3 {
+ font-size: 0.95rem;
+ font-weight: 700;
+ letter-spacing: -0.01em;
+ margin: 1.6rem 0 0.5rem;
+}
+.md-typeset h4 {
+ font-size: 0.82rem;
+ font-weight: 700;
+}
+.md-typeset a {
+ color: var(--pg-accent);
+ text-decoration: none;
+ font-weight: 500;
+}
+/* Darker blue for prose links in light mode (leaves the nav/accent untouched). */
+[data-md-color-scheme="default"] .md-typeset a {
+ color: #2e59cf;
+}
+/* Keep links bold when wrapped in **bold** (our 500 weight was overriding it). */
+.md-typeset strong a,
+.md-typeset a strong {
+ font-weight: 700;
+}
+/* Links inside headings should take the heading's weight, not the link 500. */
+.md-typeset h1 a,
+.md-typeset h2 a,
+.md-typeset h3 a,
+.md-typeset h4 a,
+.md-typeset h5 a,
+.md-typeset h6 a {
+ font-weight: inherit;
+}
+.md-typeset a:hover {
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+.md-typeset hr {
+ border-bottom: 1px solid var(--pg-border);
+}
+.md-typeset blockquote {
+ border-left: 3px solid var(--pg-border-strong);
+ color: var(--pg-muted);
+}
+
+/* ===== Sidebar navigation ===== */
+.md-nav {
+ font-size: 0.7rem;
+}
+/* Breathing room below the "PgDog" title before the first menu item.
+ Also drop Material's bg-colored box-shadow glow on the sticky title, which
+ bleeds onto the first item as an odd fade (the title bg is already solid). */
+.md-sidebar--primary .md-nav__title {
+ padding-bottom: 1rem;
+ box-shadow: none;
+}
+/* Slightly larger left-menu text at every nesting level (the right-hand TOC,
+ which lives in the secondary sidebar, keeps the base size). rem avoids the
+ size compounding as nav levels nest. */
+.md-sidebar--primary .md-nav {
+ font-size: 0.8rem;
+}
+.md-nav__link {
+ border-radius: 8px;
+ padding: 0.3rem 0.55rem;
+ margin: 1px 0;
+ color: var(--pg-muted);
+ transition: background var(--transition-speed) ease, color var(--transition-speed) ease;
+}
+/* Expandable sections wrap an inner link + arrow in a .md-nav__link container.
+ Reset the nested link's padding (Material does this) so the text lines up
+ with leaf links instead of getting doubled-up left offset. */
+.md-nav__link > .md-nav__link {
+ padding: 0;
+ margin: 0;
+}
+.md-nav__link:focus,
+.md-nav__link:hover {
+ background-color: var(--pg-hover);
+ color: var(--pg-text);
+}
+/* Consistent, scheme-aware contrast for every left-menu AND right-TOC item
+ (links and expandable section labels alike): --pg-text is near-black in light
+ mode and near-white in dark mode. Active links keep the accent; the uppercase
+ top-level group labels stay faint via their higher-specificity rule below. */
+.md-sidebar--primary .md-nav__link:not(.md-nav__link--active),
+.md-sidebar--secondary .md-nav__link:not(.md-nav__link--active) {
+ color: var(--pg-text);
+}
+.md-nav__item .md-nav__link--active,
+.md-nav__link--active {
+ color: var(--pg-accent);
+ background-color: var(--pg-accent-soft);
+ font-weight: 650;
+}
+/* For expandable sections the active link is the zero-padded inner link, which
+ would make the highlight pill too thin. Move the highlight to the container,
+ which keeps the full row padding and the correct left edge. */
+.md-nav__container:has(> .md-nav__link--active) {
+ background-color: var(--pg-accent-soft);
+ border-radius: 8px;
+ font-weight: 650;
+}
+.md-nav__container > .md-nav__link--active {
+ background-color: transparent;
+}
+/* Top-level section labels: small uppercase, like the design's nav groups. */
+.md-nav--primary > .md-nav__list > .md-nav__item--section > .md-nav__link,
+.md-nav--primary > .md-nav__list > .md-nav__item > label.md-nav__link {
+ text-transform: uppercase;
+ font-size: 0.62rem;
+ font-weight: 700;
+ letter-spacing: 0.07em;
+ color: var(--pg-faint);
+ background: transparent;
+ margin-top: 0.6rem;
+}
+.md-nav__item--section > .md-nav__link:hover {
+ background: transparent;
+ color: var(--pg-faint);
+}
-[data-md-color-scheme="slate"] .md-header {
- --md-primary-fg-color: rgb(30, 58, 138);
- background-color: hsla(230, 15%, 14%, 0.85);
+/* ===== Table of contents (right rail) ===== */
+.md-nav--secondary .md-nav__title {
+ text-transform: uppercase;
+ font-size: 0.62rem;
+ font-weight: 700;
+ letter-spacing: 0.07em;
+ color: var(--pg-faint);
+}
+.md-nav--secondary .md-nav__link {
+ border-left: 2px solid var(--pg-border);
+ border-radius: 0;
+ padding-left: 0.8rem;
+ margin: 1px 0;
+}
+.md-nav--secondary .md-nav__link:hover {
+ background: transparent;
+ color: var(--pg-text);
+}
+.md-nav--secondary .md-nav__link--active {
+ background: transparent;
+ border-left-color: var(--pg-accent);
+ color: var(--pg-accent);
+ font-weight: 600;
}
/* ===== Tables ===== */
@@ -21,143 +383,302 @@ table {
table-layout: fixed !important;
display: table !important;
}
-
.md-typeset table:not([class]) {
- border-radius: var(--card-border-radius);
+ border-radius: var(--pg-radius);
overflow: hidden;
- border: 1px solid var(--md-default-fg-color--lightest);
- font-size: 0.75rem;
+ border: 1px solid var(--pg-border);
+ font-size: 0.76rem;
+ box-shadow: none;
}
-
.md-typeset table:not([class]) th {
- background-color: var(--md-default-fg-color--lightest);
+ background-color: var(--pg-bg-subtle);
+ color: var(--pg-text);
+ font-weight: 650;
+ border-bottom: 1px solid var(--pg-border-strong);
+}
+.md-typeset table:not([class]) td {
+ border-top: 1px solid var(--pg-border);
+}
+
+/* ===== Code blocks ===== */
+.md-typeset pre > code {
+ border-radius: 0;
+}
+.md-typeset .highlight {
+ border-radius: 0;
+ overflow: hidden;
+ border: 1px solid var(--pg-border);
+}
+.md-typeset .highlight > pre,
+.md-typeset pre {
+ margin: 0;
+}
+/* Titled code blocks: the filename's default margin-top can't collapse inside
+ our bordered, overflow-hidden container, leaving an empty gap above it.
+ Pin it flush so the title sits at the top of the block. */
+.md-typeset .highlight > .filename {
+ margin-top: 0;
+ border-bottom: 1px solid color-mix(in srgb, var(--pg-faint) 35%, var(--pg-border-strong));
+}
+/* Language label shown by pymdownx.highlight */
+.md-typeset .highlight [data-md-annotation-id],
+.md-typeset .highlighttable .linenos {
+ background: var(--pg-bg-subtle);
+}
+/* Code blocks: a touch smaller than body text. */
+.md-typeset pre > code,
+.md-typeset .highlight code,
+.md-typeset .md-code__content {
+ font-size: 0.72rem;
+}
+
+/* ===== Inline code ===== */
+.md-typeset :not(pre) > code {
+ font-size: 0.9em;
+ padding: 0.1em 0.4em;
+ border-radius: 6px;
+ background-color: var(--pg-bg-code);
+ border: 1px solid var(--pg-border);
+ color: var(--pg-text);
+}
+/* Inline code reads small, so give it a bit more contrast on the light theme. */
+[data-md-color-scheme="default"] .md-typeset :not(pre) > code {
+ background-color: #e6e8ee;
+ border-color: var(--pg-border-strong);
+}
+
+/* Monospace text WITHOUT the inline-code box (no background / border).
+ Usage in Markdown (attr_list + md_in_html are enabled):
+ some text
+ Or strip the box off real inline code: `text`{ .mono } */
+.md-typeset .mono,
+.md-typeset code.mono {
+ font-family: ui-monospace, "JetBrains Mono", "SF Mono", Menlo, Consolas, monospace;
+}
+.md-typeset code.mono {
+ background: none;
+ border: none;
+ padding: 0;
+ font-size: 0.9em;
+}
+
+/* ===== Admonitions ===== */
+/* Whole block softly tinted, 3px colored left border, colored title. */
+.md-typeset .admonition,
+.md-typeset details {
+ border: 1px solid var(--pg-border);
+ border-left: 3px solid var(--pg-accent);
+ border-radius: 0;
+ background-color: var(--pg-accent-soft);
+ box-shadow: none;
+ font-size: 0.76rem;
+}
+.md-typeset .admonition-title,
+.md-typeset summary {
+ background-color: transparent !important;
+ color: var(--pg-accent);
+ font-weight: 700;
+ border: none;
+}
+.md-typeset .admonition-title::before,
+.md-typeset summary::before {
+ background-color: var(--pg-accent);
+}
+
+/* warning family → amber */
+.md-typeset .admonition.warning,
+.md-typeset .admonition.caution,
+.md-typeset .admonition.attention,
+.md-typeset details.warning,
+.md-typeset details.caution {
+ background-color: var(--pg-amber-bg);
+ border-color: var(--pg-amber-bd);
+ border-left-color: var(--pg-amber);
+}
+.md-typeset .warning > .admonition-title,
+.md-typeset .caution > .admonition-title,
+.md-typeset .attention > .admonition-title,
+.md-typeset .warning > summary,
+.md-typeset .caution > summary {
+ color: var(--pg-amber);
+}
+.md-typeset .warning > .admonition-title::before,
+.md-typeset .caution > .admonition-title::before,
+.md-typeset .attention > .admonition-title::before,
+.md-typeset .warning > summary::before,
+.md-typeset .caution > summary::before {
+ background-color: var(--pg-amber);
+}
+
+/* success / tip-as-success family → green */
+.md-typeset .admonition.success,
+.md-typeset .admonition.check,
+.md-typeset .admonition.done,
+.md-typeset details.success {
+ background-color: var(--pg-green-bg);
+ border-color: var(--pg-green-bd);
+ border-left-color: var(--pg-green);
+}
+.md-typeset .success > .admonition-title,
+.md-typeset .check > .admonition-title,
+.md-typeset .done > .admonition-title,
+.md-typeset .success > summary {
+ color: var(--pg-green);
+}
+.md-typeset .success > .admonition-title::before,
+.md-typeset .check > .admonition-title::before,
+.md-typeset .done > .admonition-title::before,
+.md-typeset .success > summary::before {
+ background-color: var(--pg-green);
+}
+
+/* danger family → red */
+.md-typeset .admonition.danger,
+.md-typeset .admonition.error,
+.md-typeset .admonition.failure,
+.md-typeset .admonition.bug,
+.md-typeset details.danger,
+.md-typeset details.error,
+.md-typeset details.bug {
+ background-color: var(--pg-red-bg);
+ border-color: var(--pg-red-bd);
+ border-left-color: var(--pg-red);
+}
+.md-typeset .danger > .admonition-title,
+.md-typeset .error > .admonition-title,
+.md-typeset .failure > .admonition-title,
+.md-typeset .bug > .admonition-title,
+.md-typeset .danger > summary,
+.md-typeset .error > summary,
+.md-typeset .bug > summary {
+ color: var(--pg-red);
+}
+.md-typeset .danger > .admonition-title::before,
+.md-typeset .error > .admonition-title::before,
+.md-typeset .failure > .admonition-title::before,
+.md-typeset .bug > .admonition-title::before,
+.md-typeset .danger > summary::before,
+.md-typeset .error > summary::before,
+.md-typeset .bug > summary::before {
+ background-color: var(--pg-red);
+}
+
+/* ===== Content tabs (pymdownx.tabbed) ===== */
+.md-typeset .tabbed-labels > label {
+ font-size: 0.7rem;
font-weight: 600;
- text-transform: uppercase;
- font-size: 0.65rem;
- letter-spacing: 0.05em;
+ color: var(--pg-muted);
+}
+.md-typeset .tabbed-set > input:checked + label {
+ color: var(--pg-accent);
+}
+.md-typeset .tabbed-labels {
+ box-shadow: inset 0 -1px var(--pg-border);
}
/* ===== Screenshots ===== */
.screenshot {
- border-radius: var(--card-border-radius);
- border: 1px solid var(--md-default-fg-color--lightest);
+ border-radius: var(--pg-radius);
+ border: 1px solid var(--pg-border);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
-/* ===== Theme-aware Images ===== */
+/* ===== Theme-aware images ===== */
.theme-aware-image {
filter: contrast(1.04) saturate(1.08);
}
-
[data-md-color-scheme="slate"] .theme-aware-image {
filter: invert(1) hue-rotate(180deg) contrast(1.12) saturate(1.05);
}
-/* ===== Card Grid ===== */
+/* ===== Card grid (card_grid / next_steps_links macros) ===== */
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 1rem;
margin: 1.5rem 0;
}
-
.grid > div {
+ position: relative;
padding: var(--card-padding);
- border: 1px solid var(--md-default-fg-color--lightest);
- border-radius: var(--card-border-radius);
- transition: all var(--transition-speed) ease;
+ border: 1px solid var(--pg-border);
+ border-radius: var(--pg-radius-lg);
background: var(--md-default-bg-color);
+ transition: border-color var(--transition-speed) ease, box-shadow var(--transition-speed) ease, transform var(--transition-speed) ease;
+}
+/* Make the whole card clickable: stretch the title link over the card. */
+.md-typeset .grid > div h4 a::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: 1;
}
-
.grid > div:hover {
- border-color: var(--md-primary-fg-color);
+ border-color: var(--pg-accent);
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transform: translateY(-2px);
}
-
+.grid > div .card-icon {
+ font-size: 1.6rem;
+ margin-bottom: 0.5rem;
+ display: block;
+}
.grid > div h4 {
margin: 0 0 0.5rem 0;
- font-size: 0.9rem;
+ font-size: 0.85rem;
}
-
-.grid > div h4 a {
- color: var(--md-primary-fg-color);
+.md-typeset .grid > div h4 a {
text-decoration: none;
- font-weight: 600;
+ font-weight: 650;
}
-
.grid > div p {
margin: 0;
- font-size: 0.82rem;
- color: var(--md-default-fg-color--light);
- line-height: 1.5;
-}
-
-/* ===== Code Blocks ===== */
-.md-typeset pre > code {
- border-radius: var(--card-border-radius);
-}
-
-.md-typeset code {
- border-radius: 4px;
- font-size: 0.82em;
-}
-
-.md-typeset .highlight {
- border-radius: var(--card-border-radius);
- overflow: hidden;
-}
-
-/* ===== Inline Code ===== */
-.md-typeset :not(pre) > code {
- padding: 0.1em 0.4em;
- background-color: var(--md-code-bg-color);
- border: 1px solid var(--md-default-fg-color--lightest);
-}
-
-/* ===== Admonitions ===== */
-.md-typeset .admonition,
-.md-typeset details {
- border-radius: var(--card-border-radius);
- border-width: 0 0 0 4px;
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.05);
-}
-
-/* ===== Navigation ===== */
-.md-nav__item .md-nav__link--active {
- font-weight: 600;
+ font-size: 0.78rem;
+ color: var(--pg-text);
+ line-height: 1.55;
}
-/* ===== Content Width ===== */
+/* ===== Content width & footer ===== */
.md-grid {
max-width: 1400px;
}
-
-/* ===== Footer ===== */
.md-footer {
margin-top: 2rem;
}
+/* Light mode: Material's footer (prev/next nav bar) is dark in both schemes.
+ Make it a light, subtle bar with dark text. */
+[data-md-color-scheme="default"] .md-footer {
+ --md-footer-bg-color: var(--pg-bg-subtle);
+ --md-footer-bg-color--dark: var(--pg-bg-subtle);
+ --md-footer-fg-color: var(--pg-text);
+ --md-footer-fg-color--light: var(--pg-muted);
+ --md-footer-fg-color--lighter: var(--pg-faint);
+ color: var(--pg-text);
+ border-top: 1px solid var(--pg-border);
+}
+/* Hide the bottom meta bar ("Made with MkDocs" + social/GitHub icons). */
+.md-footer-meta {
+ display: none;
+}
-/* ===== Hero Section (homepage) ===== */
+/* ===== Hero section (homepage) ===== */
.hero {
text-align: center;
padding: 2rem 0 1rem;
}
-
.hero h1 {
font-size: 2.2rem;
- font-weight: 700;
+ font-weight: 800;
+ letter-spacing: -0.03em;
margin-bottom: 0.5rem;
}
-
.hero p {
font-size: 1.1rem;
- color: var(--md-default-fg-color--light);
+ color: var(--pg-muted);
max-width: 640px;
margin: 0 auto 1.5rem;
line-height: 1.6;
}
-
.hero .hero-buttons {
display: flex;
gap: 0.75rem;
@@ -165,58 +686,53 @@ table {
flex-wrap: wrap;
margin-bottom: 1rem;
}
-
.hero .hero-buttons a {
display: inline-flex;
align-items: center;
gap: 0.4rem;
padding: 0.6rem 1.4rem;
- border-radius: 6px;
+ border-radius: 8px;
font-weight: 600;
font-size: 0.88rem;
text-decoration: none;
transition: all var(--transition-speed) ease;
}
-
.hero .hero-buttons .btn-primary {
- background-color: var(--md-primary-fg-color);
- color: var(--md-primary-bg-color);
+ background-color: var(--pg-accent);
+ color: #ffffff;
}
-
.hero .hero-buttons .btn-primary:hover {
- opacity: 0.9;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ opacity: 0.92;
+ box-shadow: 0 4px 12px rgba(63, 111, 232, 0.3);
}
-
.hero .hero-buttons .btn-secondary {
- border: 1px solid var(--md-default-fg-color--lighter);
- color: var(--md-default-fg-color);
+ border: 1px solid var(--pg-border-strong);
+ color: var(--pg-text);
}
-
.hero .hero-buttons .btn-secondary:hover {
- border-color: var(--md-primary-fg-color);
- color: var(--md-primary-fg-color);
+ border-color: var(--pg-accent);
+ color: var(--pg-accent);
}
-/* ===== Feature Cards (icons) ===== */
-.grid > div .card-icon {
- font-size: 1.6rem;
- margin-bottom: 0.5rem;
- display: block;
+/* ===== Responsive ===== */
+/* Material bumps the root font-size up on wide viewports (137.5% at 100em,
+ 150% at 125em). Pin it to the base size so text stays consistent across
+ widths instead of growing on large displays. */
+@media screen and (min-width: 100em) {
+ html {
+ font-size: 125%;
+ }
}
-
-/* ===== Tabs ===== */
-.md-typeset .tabbed-labels > label {
- font-size: 0.75rem;
- font-weight: 600;
+@media screen and (min-width: 125em) {
+ html {
+ font-size: 125%;
+ }
}
-/* ===== Responsive ===== */
@media screen and (max-width: 76.25em) {
.hero h1 {
font-size: 1.6rem;
}
-
.hero p {
font-size: 0.95rem;
}