使用Docker自动设置PostgreSQL

展示一些使用 Docker Compose 配置 PostgreSQL 本地开发环境的技巧。

从基本设置开始:

version: "3"
services:
    postgres:
        image: postgres:15.1-alpine
        container_name: postgres
        environment:
            - POSTGRES_DB=postgres
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=Password12!
        ports:
            -
"5432:5432"

最基本的设置,此外还通过环境变量添加两个额外的配置:

  • POSTGRES_DB - 指示 PostgreSQL 容器自动创建具有提供的名称的默认数据库,
  • POSTGRES_USER - 设置默认用户名。
  • POSTGRES_PASSWORD - 为 PostgreSQL 用户设置自定义密码。

如果我们运行:
docker-compose up

然后,将创建一个新的 PostgreSQL 容器,并在localhost:5432上可用(因为我们也向主机公开了这个端口)。我们现在可以尝试连接我们的应用程序。

如果我们想设置一个基本的数据库结构,比如一组预定义的表、索引、数据等,该怎么办?

大多数关系数据库都支持一个特殊的docker-entrypoint-initdb.d文件夹。此文件夹用于在首次创建容器时自动初始化数据库。您可以将.sql或.sh脚本放在那里,Docker 会自动执行。这只会在容器首次启动时发生,而不会在后续重新启动时发生。

让我们尝试一下并添加一个名为001-init.sql的基本脚本:

BEGIN;

-- structure setup

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL
);

-- data setup

INSERT INTO users (username, email) 
VALUES ('user1', 'user1@example.com');

INSERT INTO users (username, email) 
VALUES ('user2', 'user2@example.com');

COMMIT;

这是一个简单的脚本,表明我们可以以事务方式运行脚本(参见BEGIN和COMMIT)。我们还可以设置数据库结构并插入一些数据。
我们可以将脚本拆分为两个文件:001-init-structure.sql和002-init-data.sql。它们将按字母顺序运行。

如何将它放入容器中呢?我们可以使用volumes卷来实现这一点:

卷可以存储容器数据。您可以重新启动容器,数据将保留。它还允许将主机操作系统文件挂载/绑定到容器。它可以双向进行,您可以将文件发送到容器,但您也可以在主机存储中查看从容器生成的文件。

version: "3"
services:
    postgres:
        image: postgres:15.1-alpine
        container_name: postgres
        environment:
            - POSTGRES_DB=postgres
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=Password12!
        ports:
            -
"5432:5432"
    # VOLUMES CONFIG
        volumes:
            - ./docker/postgres:/docker-entrypoint-initdb.d

其中./docker/postgres是相对于 Docker Compose 文件的本地文件夹路径。我们可以将我们的 init 文件放在那里,它们将在构建期间自动复制到 Docker 容器,然后在 Docker 容器实例首次运行时运行。这非常简单且有用,不是吗?


创建更多数据库
默认的postgres数据库外,还将创建blogs和auth两个数据库。

我们可以添加一个名为000-create-multiple-postgresql-databases.sh的新脚本,并在其中放入以下脚本:

#!/bin/bash

set -e
set -u

function create_database() {
    local database=$1
    echo "  Creating Database '$database' for '$POSTGRES_USER'"
    psql -v ON_ERROR_STOP=1 --username
"$POSTGRES_USER" <<-EOSQL
        CREATE DATABASE $database;
        GRANT ALL PRIVILEGES ON DATABASE $database TO $POSTGRES_USER;
EOSQL
}

if [ -n
"$POSTGRES_MULTIPLE_DATABASES" ]; then
    echo
"Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES"
    for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do
        create_database $db
    done
    echo
"Multiple databases created"
fi

它检查是否存在POSTGRES_MULTIPLE_DATABASES环境变量。如果有,它会通过用逗号分隔值来获取数据库名称。然后,它会运行create_database函数,该函数会创建一个新数据库并向通过POSTGRES_USER环境变量提供的用户授予所有权限。

现在,如果我们将此文件放入./docker/postgres文件夹,它将在我们的脚本之前自动运行。这是因为我们将此文件夹映射到卷,并且脚本按字母顺序运行。

我们需要将 POSTGRES_DB更改为POSTGRES_MULTIPLE_DATABASES:

version: "3"
services:
    postgres:
        image: postgres:15.1-alpine
        container_name: postgres
        environment:
            # UPDATED TO MULTIPLE DATABASES
            - POSTGRES_MULTIPLE_DATABASES=
"postgres,blogs,auth"
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=Password12!
        ports:
            -
"5432:5432"
        volumes:
            - ./docker/postgres:/docker-entrypoint-initdb.d


使用Web IDE
我们有一个完全设置的数据库,并可以从我们的应用程序连接到它,但是有一个 IDE 来查看数据不是很好吗?

PostgreSQL 有一个不错的开源 Web IDE,名为pgAdmin。可以将其用作 Docker 镜像。让我们通过扩展配置来实现它!

version: "3"
services:
    postgres:
        image: postgres:15.1-alpine
        container_name: postgres
        environment:
            - POSTGRES_MULTIPLE_DATABASES=
"postgres,blogs,auth"
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=Password12!
        ports:
            -
"5432:5432"
        volumes:
            - ./docker/postgres:/docker-entrypoint-initdb.d


    pgadmin:
        container_name: pgadmin_container
        image: dpage/pgadmin4
        environment:
            - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
            - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD:-postgres}
            - PGADMIN_CONFIG_SERVER_MODE=False
            - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False
        ports:
            -
"${PGADMIN_PORT:-5050}:80"
        depends_on:
            - postgres


让我们讨论一下那些有点神秘的环境变量设置。

  • PGADMIN_DEFAULT_EMAIL和PGADMIN_DEFAULT_PASSWORD - 为 pgAdmin 用户设置默认凭据。pgAdmin 也可以作为常规服务托管(例如在测试环境中)并具有更高级的用户设置,但对于本地开发来说单个用户就足够了。
  • PGADMIN_CONFIG_SERVER_MODE - 确定 pgAdmin 是在服务器模式(多用户)还是桌面模式(单用户)下运行。我们将其设置为 false,这样就不会提示我们输入登录凭据。这是一个令人讨厌的废纸,我们要把它去掉。
  • PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED - 控制是否需要主密码才能访问已保存的服务器定义和其他敏感信息。通过将其设置为 false,我们可以跳过 pgAdmin 中服务器详细信息的额外密码保护层。

您可能注意到了一个奇怪的语法:${PGADMIN_DEFAULT_EMAIL: -pgadmin4@pgadmin.org }。此设置意味着,如果在主机环境中定义了PGADMIN_DEFAULT_EMAIL,则使用其值。否则,它将恢复为默认值(在我们的例子中为pgadmin4@pgadmin.org)。

您可以在 shell 中传递这样的变量:

export PGADMIN_DEFAULT_EMAIL=myemail@example.com
export PGADMIN_DEFAULT_PASSWORD=securepassword
export PGADMIN_PORT=6666
docker-compose up

或者在与我们的docker-compose.yml文件相同的文件夹中定义.env文件,Docker将自动使用它。

PGADMIN_DEFAULT_EMAIL=myemail@example.com
PGADMIN_DEFAULT_PASSWORD=securepassword
PGADMIN_PORT=6666

它对于安全性和在不修改主脚本的情况下更改变量非常有用。

回到我们的 Docker Compose 配置。如果我们现在启动容器,我们还会在http://localhost:5050上运行 pgAdmin 。

但是我们不会自动看到任何数据库

自动发现数据库
pgAdmin 不会进行任何自动发现。我们可以手动设置连接,但每次清理卷时都需要重复此操作。如果我们想在新环境中清理测试数据(可以通过运行docker compose down -v来完成),这种情况经常发生。

让我们改变它并自动设置我们的服务器列表。让我们首先在新的docker/pgAdmin文件夹中定义servers.json文件。然后放在那里:

{
    "Servers": {
       
"1": {
           
"Group": "Servers",
           
"Name": "Docker",
           
"Host": "postgres",
           
"Port": 5432,
           
"MaintenanceDB": "postgres",
           
"Username": "postgres", "
           
"Password": "Password12!",
           
"SSLMode": "prefer",
           
"Favorite": true
        }
    }
}

如您所见,我们只是在配置数据库。如果愿意,我们可以通过添加“2”:{ }”等来定义更多内容。

如果你随机生成密码(例如在 CI/CD 中),那么你也可以定义 passfile 并将“Password”: “Password12!“,替换为“PassFile”: “/pgpass”, 。然后你可以保持服务器设置不变,但只需在passfile中定义数据库密码。我们可以将其放在docker/pgAdmin文件夹中并将其放入其中:

postgres:5432:*:postgres:Password12!

让我们继续这个较长的路径来展示完整的设置。我们需要稍微调整一下我们的配置:

version: "3"
services:
    postgres:
        image: postgres:15.1-alpine
        container_name: postgres
        environment:
            - POSTGRES_MULTIPLE_DATABASES=
"postgres,blogs,auth"
            - POSTGRES_USER=postgres
            - POSTGRES_PASSWORD=Password12!
        ports:
            -
"5432:5432"
        volumes:
            - ./docker/postgres:/docker-entrypoint-initdb.d


    pgadmin:
        container_name: pgadmin_container
        image: dpage/pgadmin4
        environment:
            - PGADMIN_DEFAULT_EMAIL=${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org}
            - PGADMIN_DEFAULT_PASSWORD=${PGADMIN_DEFAULT_PASSWORD:-postgres}
            - PGADMIN_CONFIG_SERVER_MODE=False
            - PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED=False
        ports:
            -
"${PGADMIN_PORT:-5050}:80"
        depends_on:
            - postgres
        user: root
        entrypoint: /bin/sh -c
"chmod 600 /pgpass; /entrypoint.sh;"
        volumes:
            - ./docker/pgAdmin/pgpass:/pgpass
            - ./docker/pgAdmin/servers.json:/pgadmin4/servers.json

我们定义了我们的用户需要是 root,因为我们需要能够为 passfile 设置适当的权限。我们可以通过更改入口点来进行这样的设置:

/bin/sh -c "chmod 600 /pgpass; /entrypoint.sh;"

Entrypoint用于指定启动容器时执行的命令。这里我们指的是在运行常规启动命令之前,运行一个额外的 shell 脚本,使用chmod函数设置
适当的pgpass权限。

我们还映射卷以将pgpass和servers.json放入适当的文件夹中。如您所见,我们还可以映射特定文件,而不仅仅是目录。

现在,如果我们运行:

docker compose up

我们应该获得带有预配置数据库的 pgAdmin。太棒了!