如何在启动时为MongoDB容器创建一个数据库?

157
我正在使用Docker,我的堆栈包括PHP、MySQL、Apache和Redis。现在我需要添加MongoDB,因此我正在检查最新版本的Dockerfile以及MongoDB Dockerhub上的docker-entrypoint.sh文件,但我无法找到一种方法来设置默认的数据库、管理员用户/密码和可能的授权方法,以便从docker-compose.yml文件中为容器进行设置。
在MySQL中,您可以设置一些环境变量,例如:
db:
    image: mysql:5.7
    env_file: .env
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}

这将设置数据库和用户/密码为root密码。

有没有办法在MongoDB中实现相同的功能?有人有经验或解决方法吗?


你能否创建一个基于MySQL的容器,并按照你的要求进行设置,然后使用它? - Valentin
@Valentin 当然我可以,但你的观点是什么? - ReynierPM
我的意思是,您可以在Dockerfile中使用变量设置默认的DB、管理员用户名/密码和可能的身份验证方法,然后将它们传递到compose文件中。 - Valentin
注意:如果使用任何使用mongo-init脚本的解决方案,请确保为依赖于mongo的容器设置restart: unless-stopped(或除no之外的其他内容)。这是因为这些容器在初始化时第一次连接mongo时将失败(即使有depends_on标志)。请参阅此线程以获取详细信息https://dev59.com/oVwZ5IYBdhLWcg3wLdyz#35170810 - java-addict301
14个回答

189

这里是另一种更简洁的解决方案,使用docker-composejs脚本。

这个示例假设两个文件(docker-compose.yml和mongo-init.js)位于同一个文件夹中。

docker-compose.yml

version: '3.7'

services:
    mongodb:
        image: mongo:latest
        container_name: mongodb
        restart: always
        environment:
            MONGO_INITDB_ROOT_USERNAME: <admin-user>
            MONGO_INITDB_ROOT_PASSWORD: <admin-password>
            MONGO_INITDB_DATABASE: <database to create>
        ports:
            - 27017:27017
        volumes:
            - ./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro

mongo-init.js

db.createUser(
        {
            user: "<user for database which shall be created>",
            pwd: "<password of user>",
            roles: [
                {
                    role: "readWrite",
                    db: "<database to create>"
                }
            ]
        }
);

然后只需运行以下docker-compose命令启动服务

docker-compose up --build -d mongodb 

注意: 只有在数据库尚未初始化之前,才会执行 docker-entrypoint-init.d 文件夹中的代码。


9
mongo-init.js:ro是一个权限设置,它表示将mongo-init.js文件设置为只读,用于MongoDB容器中的数据初始化。 - Heril Muratovic
19
@HerilMuratovic,“volumes”条目./mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro将本地文件./mongo-init.js链接到容器路径/docker-entrypoint-initdb.d/mongo-init.js,作为只读“ro”。您可以在此处找到有关如何使用“卷”的更多详细信息:https://docs.docker.com/storage/volumes/ - Paul Wasilewski
3
@GustavoSilva 我非常肯定它正在工作。看起来你的docker-compose文件和js脚本不在同一个文件夹内。或者你是在Windows上工作?那么请调整volumes选项,并将绝对路径添加到mongo-init.js文件中,例如 c:\docker\mongo-init.js:/docker-entrypoint-initdb.d/mongo-init.js:ro - Paul Wasilewski
2
什么是db?这段代码对我来说似乎不完整,那是mongoose连接吗? - andrevenancio
3
示例已经可以正常运行并且完整。db 是当前连接的数据库。一旦容器被创建,mongo-init.js 脚本将在 mongo shell 环境中执行。 - Paul Wasilewski
显示剩余15条评论

144

/data/db目录中没有任何内容时,docker hub上的mongo映像将在/docker-entrypoint-initdb.d/中运行任何脚本。

数据库初始化

mongo容器映像提供/docker-entrypoint-initdb.d/路径以部署自定义的.js.sh设置脚本,这些脚本将在数据库初始化时运行一次。.js脚本默认会针对test执行,如果在环境变量中定义了MONGO_INITDB_DATABASE,则会以该变量为准。

COPY mysetup.sh /docker-entrypoint-initdb.d/
或者。
COPY mysetup.js /docker-entrypoint-initdb.d/

一个简单的初始化mongo shell javascript文件,演示了如何设置container集合包含数据、日志记录以及如何以错误退出(用于结果检查)。

let error = true

let res = [
  db.container.drop(),
  db.container.createIndex({ myfield: 1 }, { unique: true }),
  db.container.createIndex({ thatfield: 1 }),
  db.container.createIndex({ thatfield: 1 }),
  db.container.insert({ myfield: 'hello', thatfield: 'testing' }),
  db.container.insert({ myfield: 'hello2', thatfield: 'testing' }),
  db.container.insert({ myfield: 'hello3', thatfield: 'testing' }),
  db.container.insert({ myfield: 'hello3', thatfield: 'testing' })
]

printjson(res)

if (error) {
  print('Error, exiting')
  quit(1)
}

管理员用户设置

用于控制“root”用户设置的环境变量包括:

  • MONGO_INITDB_ROOT_USERNAME
  • MONGO_INITDB_ROOT_PASSWORD

示例

docker run -d \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=password \
  mongod

或者 Dockerfile

FROM docker.io/mongo
ENV MONGO_INITDB_ROOT_USERNAME admin
ENV MONGO_INITDB_ROOT_PASSWORD password

当检测到环境变量存在时,docker的entrypoint.sh脚本会自动添加--auth,因此您无需在命令行上使用它。


1
那个mongo PR的实现更加彻底!而且docker-entrypoint-initdb.d目录使其具有可扩展性。希望可以合并进去。 - Matt
1
已合并(请参见此处 - ReynierPM
我在使用MONGO_INITDB_DATABASE初始化空数据库时遇到了问题,因为我认为需要一个脚本。您能否为名为container的数据库添加一个这样的脚本示例? - ReynierPM
@ReynierPM 添加了一个初始化示例。 - Matt
自定义脚本如何与Docker Compose配合工作? - jzqa
显示剩余10条评论

91

不需要提前创建数据库(它们是动态创建的),但第一次容器启动时创建一个非根用户是有意义的。

docker-compose.yml:

services:
  ...
  mongo:
    image: mongo:6-jammy
    env_file: .env-mongo
    volumes:
      - mongo:/data/db
      - ./init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js
      # - ./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh  # mongo < 6

volumes:
  mongo:

init-mongo.sh 不需要可执行权限。它被引用。

.env (应用程序变量):

MONGO_USER=user
MONGO_PASSWORD=userpasswd
MONGO_DB=foo

.env-mongo(Mongo服务的变量):

MONGO_INITDB_ROOT_USERNAME=root
MONGO_INITDB_ROOT_PASSWORD=rootpasswd
MONGO_INITDB_DATABASE=foo  # for mongo >= 6

mongo-6版本开始,可以使用process.env,因此使用init-mongo.js。 在此之前,请使用init-mongo.sh(更改您在docker-compose.yml中附加的脚本,并且在这种情况下不需要MONGO_INITDB_DATABASE)。

.env是应用程序服务使用的,.env-mongomongo服务使用的。

MONGO_INITDB_*变量由mongo的entrypoint使用。 文档可以在Docker Hub上找到。 此外,正是entrypoint 运行了初始化脚本。

init-mongo.js:

db.getSiblingDB('admin').auth(
    process.env.MONGO_INITDB_ROOT_USERNAME,
    process.env.MONGO_INITDB_ROOT_PASSWORD
);
db.createUser({
    user: process.env.MONGO_USER,
    pwd: process.env.MONGO_PASSWORD,
    roles: ["readWrite"],
});

init-mongo.sh:

q_MONGO_USER=`jq --arg v "$MONGO_USER" -n '$v'`
q_MONGO_PASSWORD=`jq --arg v "$MONGO_PASSWORD" -n '$v'`
mongo -u "$MONGO_INITDB_ROOT_USERNAME" -p "$MONGO_INITDB_ROOT_PASSWORD" admin <<EOF
    use foo;
    db.createUser({
        user: $q_MONGO_USER,
        pwd: $q_MONGO_PASSWORD,
        roles: ["readWrite"],
    });
EOF

gist中有更多文件。


31
我刚刚花了三个小时搜索如何最初创建自己的数据库。 疯狂的是,文档没有提到您需要在.js或.sh文件中进行身份验证。 互联网上的示例根本不完整。 您实际上需要深入其.sh脚本以确定如何编写此内容。 如何在容器启动时“创建空数据库”。 应该为此创建PR以简化操作。 如果您问我,这有点疯狂。 - cool
2
我花了好几个小时寻找解决这个问题的方法,而这个方法刚刚救了我。谢谢。 - NaijaProgrammer
1
现在应该使用 $MONGO_INITDB_ROOT_PASSWORD 替换 $(cat "$MONGO_INITDB_ROOT_PASSWORD_FILE"),因为Mongo镜像已经为我们处理了密码保密。https://hub.docker.com/_/mongo - FlippingBinary
这是对我有效的解决方案,谢谢! - Mike
1
@JustRandom 你可以查看更新后的答案。 - x-yuri
显示剩余5条评论

67

以下是一个可用的解决方案,它创建了一个带有密码的 admin-user 用户,另外还创建了一个数据库 (test-database) ,并在该数据库中创建 test-user

Dockerfile:

FROM mongo:4.0.3

ENV MONGO_INITDB_ROOT_USERNAME admin-user
ENV MONGO_INITDB_ROOT_PASSWORD admin-password
ENV MONGO_INITDB_DATABASE admin

ADD mongo-init.js /docker-entrypoint-initdb.d/

mongo-init.js:

db.auth('admin-user', 'admin-password')

db = db.getSiblingDB('test-database')

db.createUser({
  user: 'test-user',
  pwd: 'test-password',
  roles: [
    {
      role: 'root',
      db: 'test-database',
    },
  ],
});

理解*.js文件是以未经身份验证的方式运行的是比较棘手的部分。 解决方案是将脚本作为admin-useradmin数据库中进行身份验证。 MONGO_INITDB_DATABASE admin至关重要,否则该脚本将针对test db执行。 请检查docker-entrypoint.sh的源代码。


1
角色中有错别字吗?db 应该是 test-database 吗? - Winster
第二行有getSiblingDB的两个注释。首先,在没有这一行的情况下,我的测试都能正常工作。其次,像这样覆盖db变量似乎是不好的形式,因为它被认为是一个全局对象,覆盖它会改变所有内容的上下文。 - Lazor

15

如果有人正在寻找如何使用docker-compose配置具有身份验证的MongoDB,这里是一个使用环境变量的示例配置:

version: "3.3"

services:

  db:
      image: mongo
      environment:
        - MONGO_INITDB_ROOT_USERNAME=admin
        - MONGO_INITDB_ROOT_PASSWORD=<YOUR_PASSWORD>
      ports:
        - "27017:27017"

运行docker-compose up时,您的Mongo实例将自动运行,并启用验证。 您将拥有一个带有给定密码的管理员数据库。


4
mongo --username admin --password password --host localhost --port 27017 i am not able to connect as the logs show the users and db is created successfully. ERROR: $ mongo --username admin --password password --host localhost --port 27017 MongoDB shell version v3.4.10 connecting to: mongodb://localhost:27017/ MongoDB server version: 3.6.5 WARNING: shell and server versions do not match 2018-06-07T13:05:09.022+0000 E QUERY [thread1] Error: Authentication failed. : DB.prototype._authOrThrow@src/mongo/shell/db.js:1461:20 @(auth):6:1 @(auth):1:2 exception: login failed - Tara Prasad Gurung
1
你需要使用 mongo admin --username ADMIN --password PASSWORT 命令来连接到你的Mongo实例。 - Philipp Schemel
1
无法使其工作。“找不到用户admin@admin”。它似乎不能自动创建用户? - batjko
mongodb://root:example@localhost/my-db?authSource=admin - FDisk
2
@TaraPrasadGurung 我曾经遇到过这个问题,发现我已经为数据库创建了一个卷,你必须删除该卷才能为容器创建新用户。 - Daniel S.
遇到了同样的问题,但是@DanielS.建议删除卷解决了这个问题。谢谢。 - Geoherna

15

这对我有用:

docker-compose.yaml

version: "3.8"

services:
  mongodb:
    image: mongo:3.6
    restart: always
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=hello
    volumes:
      - ./mongo-init/:/docker-entrypoint-initdb.d/:ro
    ports:
      - 27017:27017
      - 9229:9229

  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8111:8081
    environment:
      - ME_CONFIG_MONGODB_SERVER=mongodb
      - ME_CONFIG_MONGODB_ADMINUSERNAME=root
      - ME_CONFIG_MONGODB_ADMINPASSWORD=hello

./mongo-init/init.js

conn = new Mongo();
db = conn.getDB("MyDatabaseName");


db.myCollectionName.createIndex({ "address.zip": 1 }, { unique: false });

db.myCollectionName.insert({ "address": { "city": "Paris", "zip": "123" }, "name": "Mike", "phone": "1234" });
db.myCollectionName.insert({ "address": { "city": "Marsel", "zip": "321" }, "name": "Helga", "phone": "4321" });

请通过 http://localhost:8111/ 查看仪表板:

输入图片描述


3
只是想知道,暴露9229的目的是什么?根据参考文献,例如https://docs.mongodb.com/manual/reference/default-mongodb-port/,mongod默认可能会占用27017、27018、27019。 - nakhodkin
太棒了的例子!简单明了! - undefined

10

我是如何使用环境变量和密钥的。

以下内容将在Mongo容器启动时创建“app_user”和“app_database”(仅当使用存储在/data/db中的数据库为空时)。

Dockerfile

FROM mongo:5.0.3

COPY images/mongo/init.js /docker-entrypoint-initdb.d/

init.js

db.log.insertOne({"message": "Database created."});

db.createUser(
    {
        user: _getEnv("MONGO_USER"),
        pwd: cat(_getEnv("MONGO_PASSWORD_FILE")),
        roles: [
            "readWrite", "dbAdmin"
        ]
    }
);
  • 脚本将使用docker-compose.yml中定义的MONGO_INITDB_DATABASE环境变量作为数据库名称。
  • /docker-entrypoint-initdb.d/目录中的脚本仅在/data/db存储的数据库为空时才会运行。

docker-compose.yml

mongo:
  build:
    context: .
    dockerfile: ./images/mongo/Dockerfile
  container_name: app_mongo
  environment:
    MONGO_INITDB_DATABASE: "app_database"
    MONGO_INITDB_ROOT_PASSWORD_FILE: /run/secrets/app_mongo_root_password
    MONGO_INITDB_ROOT_USERNAME: "root"
    MONGO_PASSWORD_FILE: /run/secrets/app_mongo_password
    MONGO_USER: "app_user"
  secrets:
    - app_mongo_password
    - app_mongo_root_password
  volumes:
    - mongo:/data/db

secrets:
  app_mongo_password:
    file: ./secrets/app_mongo_password.txt
  app_mongo_root_password:
    file: ./secrets/app_mongo_root_password.txt

volumes:
  mongo:

1
init.js 中的 _getEnv 函数是从哪里来的? - Mohsen Taleb
1
从Mongo - https://github.com/docker-library/mongo/issues/257#issuecomment-824117838 - Patrik H.
需要注意的是,这个程序需要更新到新版本:>= 4.7.0。 - Max O.

10

给定以下的 .env 文件:

DB_NAME=foo
DB_USER=bar
DB_PASSWORD=baz

还有这个mongo-init.sh文件:

mongo --eval "db.auth('$MONGO_INITDB_ROOT_USERNAME', '$MONGO_INITDB_ROOT_PASSWORD'); db = db.getSiblingDB('$DB_NAME'); db.createUser({ user: '$DB_USER', pwd: '$DB_PASSWORD', roles: [{ role: 'readWrite', db: '$DB_NAME' }] });"

这个docker-compose.yml文件将会创建管理员数据库和管理员用户,并以管理员用户身份进行验证,接着创建真正的数据库并添加真实用户:

version: '3'

services:
#  app:
#    build: .
#    env_file: .env
#    environment:
#      DB_HOST: 'mongodb://mongodb'

  mongodb:
    image: mongo:4
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin-user # change this
      MONGO_INITDB_ROOT_PASSWORD: admin-password # change this
      DB_NAME: $DB_NAME
      DB_USER: $DB_USER
      DB_PASSWORD: $DB_PASSWORD
    ports:
      - "27017:27017"
    volumes:
      - db-data:/data/db
      - ./mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh

volumes:
  db-data:

你为什么要使用 sh,而不是直接执行一个 js 代码行呢?为什么不使用 js 文件呢? - Max O.
@MaxO。使用mongo-init.js文件可能可以做到同样的事情 - 它能够读取process.env中的环境变量吗? - Alf Eaton
这是可能的,使用 _getEnv()(版本 >= 4.7.0),我相信只使用 $ 也是可能的。 - Max O.

9

对于这里的一些答案,我只是想补充一点 - 它确实节省了我很多时间去解决问题!

如果您要创建的用户需要附加到特定数据库,则需要添加一些代码。

最重要的是您需要在管理员基础上登录以创建新用户。

因此,对于docker-compose.yml示例,您的mongo-init.js文件应如下所示:

db = db.getSiblingDB('admin');
// move to the admin db - always created in Mongo
db.auth("rootUser", "rootPassword");
// log as root admin if you decided to authenticate in your docker-compose file...
db = db.getSiblingDB('DB_test');
// create and move to your new database
db.createUser({
'user': "dbUser",
'pwd': "dbPwd",
'roles': [{
    'role': 'dbOwner',
    'db': 'DB_test'}]});
// user created
db.createCollection('collection_test');
// add new collection

如果这可以帮助某个人,那我很高兴 - 干杯。

6

如果你想从docker-compose.yml中删除用户名和密码,你可以使用Docker Secrets,这是我的做法。

version: '3.6'

services:
  db:
    image: mongo:3
    container_name: mycontainer
  secrets:
    - MONGO_INITDB_ROOT_USERNAME
    - MONGO_INITDB_ROOT_PASSWORD
  environment:
    - MONGO_INITDB_ROOT_USERNAME_FILE=/var/run/secrets/MONGO_INITDB_ROOT_USERNAME
    - MONGO_INITDB_ROOT_PASSWORD_FILE=/var/run/secrets/MONGO_INITDB_ROOT_PASSWORD
secrets:
  MONGO_INITDB_ROOT_USERNAME:
    file:  secrets/${NODE_ENV}_mongo_root_username.txt
  MONGO_INITDB_ROOT_PASSWORD:
    file:  secrets/${NODE_ENV}_mongo_root_password.txt

我已经使用了 file: 选项来保护我的秘密信息,不过你也可以使用 external: 选项并在一个 swarm 中使用这些秘密信息。这些秘密信息可以在容器中的 /var/run/secrets 目录下被任何脚本所调用。
Docker 文档中提到了关于存储敏感数据的方法...
你可以使用 secrets 来管理容器运行时需要但又不想存储在镜像或源代码控制中的任何敏感数据,例如:
- 用户名和密码 - TLS 证书和密钥 - SSH 密钥 - 其他重要数据,如数据库名称或内部服务器名称 - 通用字符串或二进制内容(最大可达 500 KB)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接