[docker] utility container
utility container 我感觉就是工具,可以减少一些系统安装软件的容器
使用场景
主要还是系统减负,比如说正常情况下运行一个 node 项目,那么系统上一定需要安装一个 npm,哪怕是创建一个新的项目,使用 npm init
,也是需要保证本机上安装一个 npm,但是如果将其放到 docker 容器中,那么就不需要安装 npm 了
如果只是 node,那么问题倒也不是很大,但是参考一下 laravel 项目,官方文说,必须 安装 PHP 和 Composer,推荐 安装 Node 和 NPM:
这种情况下,安装工具就相对而言比较困难
另一个情况是不同的库,但是会产生冲突。比如说 MariaDB 和 MySQL,默认导出的 port 都是 3306,但是二者不兼容,包括使用的 SQL Driver 都不一样。我就因为想要同时安装 MariaDB 和 MySQL 导致一些文件冲突,然后折腾了一个下午才彻底删除干净,重新安装了 MySQL
再后来干脆就使用 MySQL 的 docker 了
使用 util container 的方法
简单介绍一下用 util container 的方式
直接 -it
这种大多数情况下是需要用 REPL,如:
❯ docker run -it --rm node
11Welcome to Node.js v21.7.3.
Type ".help" for more information.
> 1 + 1
2
> console.log('hello docker')
hello docker
undefined
>
另一种使用方式如下:
❯ docker run -it node npm init -y
Wrote to /package.json:
{
"name": "",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
这样就可以创建一个空白的 node 项目了
另外也可以在 run 的同时 attach 一个 bash,这样可以直接通过命令行执行
exec
但是有的情况下,已经使用 docker run
,又想额外加一些功能,这时候就可以使用 exec
,如:
# 假设之前的指令是启动一个 node 容器
❯ docker exec -it sweet_nightingale npm init -y
Wrote to /package.json:
{
"name": "",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
exec
是在 docker 容器内运行指令的 arg,如果想要 attach bash 的话,那么容器就一定要在运行的状态才行
bind mounts util container
之前在 [docker] 数据的持久化 - Volume & bind mounts 提到可以把 docker 容器中的路径和本机的路径进行挂载,这样双向都能够被更新
这个思路也可以应用到 util container 上,这样就可以在本机修改代码,然后在 container 中即时更新。或者容器中修改了一些内容,本机上也获取同步更新
下面就实现一个 node 的 util container 的 bind mounts:
# 使用 slim 或者 alpine 会比完整的版本小很多
❯ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
node-util latest d006cdcdfc7d 7 seconds ago 132MB
node latest b6d2a1484e51 About an hour ago 1.1GB
# 然后进行 bind mount
❯ docker run -it -v "$(pwd):/app" node-util npm init -y
Wrote to /app/package.json:
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
可以看到本机上也出现了对应的 package.json 文件
使用 Dockerfile
这里多提一下 ENTRYPOINT
,这是一个添加了之后 docker 永远会多执行的一个指令:
FROM node:20-slim
WORKDIR /app
ENTRYPOINT [ "npm" ]
这样就可以不用反复输入 npm
,因为每次执行的时候,npm
都会执行:
❯ docker run -it -v "$(pwd):/app" node-util init -y
Wrote to /app/package.json:
{
"name": "app",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
# 下载 node_module,注意这里是启动了一个新的 container
❯ docker run -it -v "$(pwd):/app" node-util install
up to date, audited 1 package in 300ms
found 0 vulnerabilities
# 安装 express,注意这里启动了第三个 container
❯ docker run -it -v "$(pwd):/app" node-util install express
added 64 packages, and audited 65 packages in 4s
12 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
npm notice
npm notice New patch version of npm available! 10.5.0 -> 10.5.2
npm notice Changelog: https://github.com/npm/cli/releases/tag/v10.5.2
npm notice Run npm install -g npm@10.5.2 to update!
npm notice
完成之后,node_modules 也会被同步到本机上:
使用 docker compose
想要继续简化步骤,则是可以使用 docker compose:
version: "3.8"
services:
npm:
build: ./
stdin_open: true
tty: true
volumes:
- ./:/app
这样可以少打一些字:
# can use --rm to remove the container after executing the command
❯ docker compose run npm init -y
WARN[0000] /Users/luhan/study/docker/section07/docker-compose.yaml: `version` is obsolete
[+] Creating 1/0
✔ Network section07_default Created 0.0s
[+] Building 0.6s (6/6) FINISHED docker:desktop-linux
=> [npm internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 90B 0.0s
=> [npm internal] load metadata for docker.io/library/node:20-slim 0.5s
=> [npm internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [npm 1/2] FROM docker.io/library/node:20-slim@sha256:7c5c3f250db850d159c5f5554a3a608a72eb721dd15451d294e3fe74f944a4fc 0.0s
=> CACHED [npm 2/2] WORKDIR /app 0.0s
=> [npm] exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:2d02c95f22951c1aea74c6c4d1cf6703115cbacbfeaccec867d55993b17894d3 0.0s
=> => naming to docker.io/library/section07-npm 0.0s
Wrote to /app/package.json:
{
"name": "app",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.19.2"
},
"devDependencies": {},
"description": ""
}
保持 node 项目运行
这个是只针对 node 的优化:
❯ docker run -d node:20-alpine tail -f /dev/null
Unable to find image 'node:20-alpine' locally
20-alpine: Pulling from library/node
4abcf2066143: Already exists
3bce96456554: Already exists
2bde47b9f7c3: Already exists
db3e2f2b6054: Already exists
Digest: sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2
Status: Downloaded newer image for node:20-alpine
1ab1731f8bdc929e6274ebc4f3e7b95596a5d0d33be1b41c4eb2726ab3c8f1ae
❯ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1ab1731f8bdc node:20-alpine "docker-entrypoint.s…" 3 seconds ago Up 2 seconds sleepy_wright
# 使用 exec 执行 sh
❯ docker exec -it sleepy_wright sh
/ # echo "bash is not present"
bash is not present
这样就可以进入到容器内执行命令,而不是反复重新起一个新的容器
⚠️:alpine 模式下 bash 不存在,所以可以使用 sh