docker 之間無限重建的小圈圈

我覺得我快受不了 docker images 之間的銜接了……就不能讓我好好寫程式嗎?

composing, sigh

這次的任務是讓我的 expressJS app 接上我的 mongoDB。盡可能不要裝 npm 套件。

首先是建資料庫。在 mongoDB 內部,事情還好解決:

version: "3.8"

services:
  mongo:
    image: mongo
    restart: always
    ports:
      - "27017:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      # Use root/example as user/password credentials
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
      ME_CONFIG_BASICAUTH: false

這可以讓 mongoDB 有個預覽用的地方能看。但到要接上 expressJS app 就是麻煩了……

      ## 省略
      ME_CONFIG_BASICAUTH: false
  express-app:
    build: ./app
    ports:
      - "3000:3000"
    depends_on:
      - mongo
    volumes:
      - "./app:/usr/src/app"
    environment:
      APP_MONGO_URL: mongodb://root:example@mongo:27017/

Dockerfile

首先我光要連接就是困難:

# app/Dockerfile
FROM node:latest
WORKDIR /usr/src/app
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npm", "app.js"]

然後說找不到 package.json 也用不到 app.js 檔案……但事實上我的 entry 檔案就是 index.js 啊。看來不能隨意複製貼上,不過我也沒別的辦法了。

改成 npm start 卻發現沒有動,又卡了很久。查了一下,才發現改了 Dockerfile 後需要 docker-compose builddocker-compose up -d 才能更新設定。

難道不能做在一起嗎?

檔案反應

接著是 expressJS app ……每次改 expressJS app 程式後,必須重開 npm start 才可以反應。不能像 rails 的 rails/bin server 或 Laravel 的 php artisan serve 那樣一聽就改動。

怎麼辦呢?你需要 nodemon 完成。也就是:

$ npm install -g nodemon
$ nodemon npm start

我不想再裝東西了欸。饒了我吧。

env

然後連資料庫呢:

import express from "express";
import mongoose from "mongoose";

const app = express();
const port = 3000;

app.get("/api", async (req, res) => {
    console.log(process.env.APP_MONGO_URL);
    const list = await mongoose.connect(process.env.APP_MONGO_URL);
});

結果程式說找不到。原來是因為 code 不會讀 .env 檔。還需要 dotenv 才可以。

我不想裝,好險 docker 的 environment 可以直接用 env 參數。

但來是有問題。

2024-04-21 22:20:23 "mongodb://root:example@mongo:27017/test2"
2024-04-21 22:20:23 Error fetching data: MongoParseError: Invalid scheme, expected connection string to start with "mongodb://" or "mongodb+srv://"
2024-04-21 22:20:23     at new ConnectionString (/usr/src/app/node_modules/mongodb-connection-string-url/lib/index.js:86:19)
2024-04-21 22:20:23     at parseOptions (/usr/src/app/node_modules/mongodb/lib/connection_string.js:192:17)
2024-04-21 22:20:23     at new MongoClient (/usr/src/app/node_modules/mongodb/lib/mongo_client.js:52:63)
2024-04-21 22:20:23     at NativeConnection.createClient (/usr/src/app/node_modules/mongoose/lib/drivers/node-mongodb-native/connection.js:293:14)
2024-04-21 22:20:23     at NativeConnection.openUri (/usr/src/app/node_modules/mongoose/lib/connection.js:801:34)
2024-04-21 22:20:23     at Mongoose.connect (/usr/src/app/node_modules/mongoose/lib/mongoose.js:414:15)
2024-04-21 22:20:23     at file:///usr/src/app/index.js:22:37
2024-04-21 22:20:23     at Layer.handle [as handle_request] (/usr/src/app/node_modules/express/lib/router/layer.js:95:5)
2024-04-21 22:20:23     at next (/usr/src/app/node_modules/express/lib/router/route.js:149:13)
2024-04-21 22:20:23     at Route.dispatch (/usr/src/app/node_modules/express/lib/router/route.js:119:3)

告訴我為什麼是錯的,好嗎?想也知道不行。

於是就這樣一直卡、一直卡、直到放棄。

為什麼 Docker 之間的 Images 這麼難連起來?

從新粗花

休息一下以後,我再度與 mondo 奮戰。這次我決定先砍掉 express-app 這個 image,只留下資料庫與前端 image 後給 mongo 開個 port,希望把麻煩弄少點。

version: "3.8"

services:
  mongo:
    image: mongo
    restart: always
    ports:
      - 27017:27017
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      # Use root/example as user/password credentials
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
      ME_CONFIG_BASICAUTH: false
import express from "express";
import mongoose from "mongoose";

const app = express();
const port = 3000;

app.get("/api", async (req, res) => {
    const list = await mongoose.connect("mongodb://root:example@127.0.0.1:27017/testdb");
});

可還是錯: MongoError: bad auth Authentication failed ──這怎搞的?我 root 還不行?
欸還真不行。因為 testdb 裡面並沒有這個用戶。

「豈有此理……」我懷著怨氣前往 docker 裡面 mongo image 的 exec 操作 mondo 的資料庫授權:

$ mongosh
$ MongoServerError[Unauthorized]: Command createUser requires authentication
$ mongosh --authenticationDatabase admin --username root --password example
$ use testdb
$ db.createUser({ user: "myuser", pwd: "mypassword", roles: [{ role: "readWrite", db: "testdb" }] });
$ exit

可以是可以了……但不覺得這麼作,太不切實際了嗎?誰會在 Docker 搞這麼麻煩?

然後我看到 Stack Overflow 這篇文章: How to create user in mongodb with docker-compose

結果才發現可以用 volumes 建立用戶:

// init-mongo.js
db.createUser({ user: "myuser", pwd: "mypassword", roles: [{ role: "readWrite", db: "testdb" }] });
version: "3.8"

services:
  mongo:
    image: mongo
    restart: always
    ports:
      - 27017:27017
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: example
      MONGO_INITDB_DATABASE: testdb
    volumes:
      - ./init-mongo.js:/docker-entrypoint-initdb.d/mongo-init.js:ro
  mongo-express:
    image: mongo-express
    restart: always
    ports:
      - 8081:8081
    environment:
      # Use root/example as user/password credentials
      ME_CONFIG_MONGODB_ADMINUSERNAME: root
      ME_CONFIG_MONGODB_ADMINPASSWORD: example
      ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/
      ME_CONFIG_BASICAUTH: false

「原來你還有一個 mongo-init.js 喔……」我嘆了嘆氣,這算什麼啊……

參考資料