{"id":19069614,"url":"https://github.com/srect/docker-compose","last_synced_at":"2025-06-27T04:33:52.441Z","repository":{"id":114172592,"uuid":"446762929","full_name":"sRect/docker-compose","owner":"sRect","description":"docker compose demo","archived":false,"fork":false,"pushed_at":"2022-01-29T08:05:16.000Z","size":782,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-22T03:41:58.868Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sRect.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-01-11T09:48:52.000Z","updated_at":"2022-01-11T09:49:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"91f0e4be-ce81-4234-8edf-a516ce0f9451","html_url":"https://github.com/sRect/docker-compose","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sRect/docker-compose","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2Fdocker-compose","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2Fdocker-compose/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2Fdocker-compose/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2Fdocker-compose/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sRect","download_url":"https://codeload.github.com/sRect/docker-compose/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sRect%2Fdocker-compose/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262188397,"owners_count":23272341,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-09T01:14:55.820Z","updated_at":"2025-06-27T04:33:52.417Z","avatar_url":"https://github.com/sRect.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## 超详细手摸手带你 docker-compose + portainer 部署你的 todolist 小应用\n\n- [项目线上预览](http://121.199.8.71:8001/)\n- [github 源码仓库](https://github.com/sRect/docker-compose)\n\n文章里很多都是配置，可以直接先直接复制过去使用，或者直接下载源代码运行，先跑通项目。\n\n### 1. 主要 package version 和提前准备\n\n1. package version\n\n|  package  | version |\n| :-------: | :-----: |\n|    koa    | ^2.13.4 |\n| sequelize | ^6.14.1 |\n|   vite    | ^2.7.13 |\n|    vue    | ^3.2.25 |\n|   pinia   | ^2.0.6  |\n\n2. 提前准备\n\n- 1 台线上服务器，百度云、阿里云等等，并安装好 docker(教程自行搜索，很简单)。没有的用本地虚拟机代替，自行研究\n- 注册一个 dockerhub 账户，有阿里云服务器的会自带容器镜像服务可以用，可以不用注册 dockerhub\n- 注册一个 github 账户，并创建一个仓库。文章以 github 为例，gitee 的自行研究\n\n### 2. 项目目录解构\n\n\u003e 项目目录结构简单，前端应用放在`web`目录内，数据库放在`mysql`目录内，其它的是后台`koa`应用\n\n```\n├── docker-compose.yml  # docker-compose配置文件\n├── Dockerfile # 后台koa的容器镜像配置文件\n├── log # 日志存储\n|  ├── access.log-2022-01-28.log\n|  ├── application.log-2022-01-28.log\n|  └── mysql.log-2022-01-28.log\n├── middleware # koa中间件\n|  ├── handleGlobalError.js\n|  ├── index.js\n|  └── logClientDevices.js\n├── mysql # mysql配置文件\n|  ├── create_db.sql # 创建数据库sql\n|  ├── Dockerfile # mysql的容器镜像配置文件\n|  ├── initial_data.sql # 初始化数据表sql\n|  ├── privileges.sql # 修改mysql账号权限密码\n|  └── setup.sh # 容器启动执行的shell\n├── package-lock.json\n├── package.json\n├── public # koa-static静态文件配置目录\n|  ├── favicon.ico\n|  └── index2.html\n├── README.md\n├── routes # @koa/router的接口路由\n|  ├── todoList.js\n|  └── uuid.js\n├── server.js # koa的启动文件\n├── util\n|  ├── db.js # mysql连接配置\n|  └── logger.js # koa-log4的日志配置\n└── web # vue前端应用\n    ├── Dockerfile\n    ├── index.html\n    ├── nginx.conf\n    ├── package.json\n    ├── public\n    ├── README.md\n    ├── src\n    |  ├── App.vue\n    |  ├── assets\n    |  ├── components\n    |  ├── main.js\n    |  ├── services # 接口\n    |  └── store # Pinia状态管理\n    ├── vite.config.js\n    └── yarn.lock\n```\n\n### 3. 前端部分\n\n\u003e 当然要用最近炒的火热的 Pinia 状态管理 + vue3 来写 todolist 应用了\n\n![todolist.jpg](./public/img/todolist.jpg)\n\n#### 3.1 [Pinia 状态管理快速上手](https://pinia.esm.dev/introduction.html#a-more-realistic-example)，对着文档开撸\n\n```javascript\n// src/store/index.js\nimport { defineStore } from \"pinia\";\nimport * as types from \"./types\";\n\nexport const useTodosStore = defineStore(\"todos\", {\n  state: () =\u003e ({\n    /** @type {{ msg: string, id: string, is_finished: boolean, create_time: date }[]} */\n    todos: [],\n    filter: types.ALL,\n    nextId: 0,\n  }),\n  getters: {\n    finishedTodos(state) {\n      return state.todos.filter((todo) =\u003e todo.is_finished);\n    },\n    unfinishedTodos(state) {\n      return state.todos.filter((todo) =\u003e !todo.is_finished);\n    },\n    filterTodos(state) {\n      if (this.filter === types.FINISHED) {\n        return this.finishedTodos;\n      } else if (this.filter === types.UNFINISHED) {\n        return this.unfinishedTodos;\n      }\n\n      return this.todos;\n    },\n  },\n  actions: {\n    addTodos({ id, msg, create_time }) {\n      this.todos.unshift({ id, msg, create_time, is_finished: false });\n    },\n    finishedOneTodo(obj) {\n      const index = this.todos.findIndex((item) =\u003e item.id === obj.id);\n      this.todos.splice(index, 1, {\n        ...obj,\n        is_finished: true,\n      });\n    },\n    deleteOne(id) {\n      this.todos = this.todos.filter((item) =\u003e item.id !== id);\n    },\n    setInitialData(arr) {\n      this.todos = [...arr];\n    },\n  },\n});\n```\n\n#### 3.2 关于 vite+element-plus 启动报错 error while updating dependencies\n\n报错部分：\n\n```\n上午11:02:22 [vite] new dependencies found: element-plus/es, element-plus/es/components/option/style/css, updating...\n上午11:02:22 [vite] Failed to load source map for /node_modules/.vite/chunk-TPOPRDHF.js?v=e12284c2.\n \u003e error: Failed to write to output file: open D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\.vite\\element-plus.js: Access is denied.\n\n上午11:02:37 [vite] error while updating dependencies:\nError: Build failed with 1 error:\nerror: Failed to write to output file: open D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\.vite\\element-plus.js: Access is denied.\n    at failureErrorWithLog (D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:1493:15)\n    at D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:1151:28\n    at runOnEndCallbacks (D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:941:63)\n    at buildResponseToResult (D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:1149:7)\n    at D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:1258:14\n    at D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:629:9\n    at handleIncomingPacket (D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:726:9)\n    at Socket.readFromStdout (D:\\my\\resource\\Vue.js\\vue3+Pinia\\node_modules\\esbuild\\lib\\main.js:596:7)\n    at Socket.emit (events.js:400:28)\n    at addChunk (internal/streams/readable.js:290:12)\n```\n\n一番折腾（又是删除 node_modules 重新 install 安装，又是升级依赖到最新版本，又是检查文件夹路径有无中文...）过后，找到了[解决方法](https://github.com/nuxt/vite/issues/207#issuecomment-972655272)：\n\n![vite.png](./public/img/vite.png)\n\n升级 Node.js 到最新版本（当前最新版本为 16.13.2），删掉 node_modules 文件夹，重新安装依赖，启动正常\n\n如果你使用 nvm，可以这样快捷升级 nodejs\n\n```bash\nnvm install 16.13.2\nnvm use 16.13.2\n```\n\n#### 3.3 nginx.conf\n\n```conf\nserver {\n    listen  80; #配置监听端口\n    charset utf-8;\n\n    #charset koi8-r;\n    #access_log  /var/log/nginx/host.access.log  main;\n\n    location ~ /api/ { # 配置跨域转发\n        # 注意这里的配置代理名称为docker-compose中koa容器的名称\n        proxy_pass  http://koa_server:4000;\n        # rewrite ^/api/(.*)$ /$1 break;\n        proxy_set_header Host $host;\n        proxy_set_header X-Real-IP $remote_addr;\n        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n        client_max_body_size 100m;\n    }\n\n    location / {\n        root   /usr/share/nginx/dist; #服务默认启动目录\n        index  index.html index.htm; #默认访问文件\n        try_files $uri /index.html; # 防止浏览器刷新后，页面404\n        client_max_body_size 100m;\n    }\n\n    location =/admin {\n        deny all; #admin目录禁止任何人访问\n    }\n\n    location ~\\.java$ {\n        deny all; #匹配所有java文件禁止访问\n    }\n\n    error_page   500 502 503 504  /50x.html; #错误状态的显示页面，配置后需重启\n    error_page 404 /404.html; #404页面\n    location = /50x.html {\n        root   /usr/share/nginx/html;\n    }\n\n    gzip on; #开启gzip\n    gzip_buffers 32 4k; #设置压缩所需要的缓冲区大小，以4k为单位，如果文件为32k则申请32*4k的缓冲区\n    gzip_comp_level 6; #gzip 压缩级别，1-9，数字越大压缩的越好，也越占用CPU时间\n    gzip_min_length 4000; #gizp压缩起点，文件大于4k才进行压缩\n    gzip_vary on; # 是否在http header中添加Vary: Accept-Encoding，建议开启\n    gzip_static on; #nginx对于静态文件的处理模块，开启后会寻找以.gz结尾的文件，直接返回，不会占用cpu进行压缩，如果找不到则不进行压缩\n    gzip_types text/xml text/javascript application/javascript text/css text/plain application/json application/x-javascript; # 进行压缩的文件类型\n}\n```\n\n#### 3.4 Dockerfile\n\n\u003e 使用多阶段构建，减少容器体积\n\n```Dockerfile\nFROM alpine:3.15 AS base\nENV NODE_ENV=production \\\n  APP_PATH=/app\nWORKDIR $APP_PATH\n# 使用apk命令安装 nodejs 和 yarn\nRUN apk add --no-cache --update nodejs=16.13.2-r0 yarn\n\nFROM base AS install\nCOPY package.json yarn.lock $APP_PATH/\nRUN yarn install\n\nFROM base AS build\n# 拷贝上面生成的 node_modules 文件夹复制到最终的工作目录下\nCOPY --from=install $APP_PATH/node_modules $APP_PATH/node_modules\n# 拷贝当前目录的文件到工作目录(除了.dockerignore中忽略的)\nCOPY . $APP_PATH/\nRUN yarn run build\n\nFROM nginx:alpine\nWORKDIR /usr/share/nginx/dist\n# 添加自己的配置 default.conf 在下面\nADD nginx.conf /etc/nginx/conf.d/default.conf\nCOPY --from=build /app/dist .\nEXPOSE 80\n```\n\n### 4. 后台部分\n\n#### 4.1 入口文件\n\n```javascript\n// server.js\nconst path = require(\"path\");\nconst Koa = require(\"koa\");\nconst Router = require(\"@koa/router\");\nconst ROOT = path.resolve(process.cwd(), \"./\");\nconst { connectMySQL } = require(path.resolve(ROOT, \"./util/db\"));\nconst todoList = require(path.resolve(ROOT, \"./routes/todoList\"));\n\nconst app = new Koa();\nconst router = new Router();\n\n// 加载所有子路由\nrouter.use(\"/api\", todoList.routes(), todoList.allowedMethods());\n// 加载路由中间件\napp.use(router.routes()).use(router.allowedMethods());\n\napp.listen(4000, async () =\u003e {\n  await connectMySQL();\n});\n```\n\n#### 4.2 [sequelize 连接 mysql 数据库](https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/core-concepts/getting-started.md)\n\n```javascript\n// util/db.js\nconst path = require(\"path\");\nconst { Sequelize } = require(\"sequelize\");\n\nconst db = new Sequelize(\"todolist\", \"root\", \"123456\", {\n  dialect: \"mysql\",\n  dialectOptions: {\n    charset: \"utf8mb4\",\n    collate: \"utf8mb4_unicode_ci\",\n    supportBigNumbers: true,\n    bigNumberStrings: true,\n  },\n  // 这里的host，线上指向docker-compose中mysql的容器名\n  host:\n    process.env.NODE_ENV === \"development\"\n      ? \"localhost\"\n      : \"todolist_mysql_server\",\n  timezone: \"+08:00\", // 东8区\n  port: \"3306\",\n});\n\nconst connectMySQL = async () =\u003e {\n  try {\n    await db.authenticate();\n    console.log(\"mysql连接成功\");\n  } catch (e) {\n    console.log(e);\n    console.log(\"连接失败，3秒后重试\");\n    setTimeout(connectMySQL, 3000);\n  }\n};\n\nexports.connectMySQL = connectMySQL;\nexports.db = db;\n```\n\n#### 4.3 接口示例\n\n```javascript\n// routes/todolist.js\nconst Router = require(\"@koa/router\");\nconst dayjs = require(\"dayjs\");\nconst utc = require(\"dayjs/plugin/utc\"); // dependent on utc plugin\nconst timezone = require(\"dayjs/plugin/timezone\");\nconst { QueryTypes } = require(\"sequelize\");\n\nconst ROOT = path.resolve(process.cwd(), \"./\");\nconst { db } = require(path.resolve(ROOT, \"./util/db\"));\n\nconst todoList = new Router();\n// https://dayjs.gitee.io/docs/zh-CN/plugin/timezone\ndayjs.extend(utc);\ndayjs.extend(timezone);\ndayjs.tz.setDefault(\"Asia/Shanghai\");\n\n// 列表查询\ntodoList.get(\"/todoList/list\", async (ctx, next) =\u003e {\n  const reqParams = ctx.query;\n  // https://github.com/demopark/sequelize-docs-Zh-CN/blob/master/core-concepts/getting-started.md#promises-%E5%92%8C-asyncawait\n\n  const selects = {\n    0: \"SELECT * FROM todolist WHERE is_finished='0' ORDER BY create_time DESC;\",\n    1: \"SELECT * FROM todolist WHERE is_finished='1' ORDER BY create_time DESC;\",\n    2: \"SELECT * FROM todolist ORDER BY create_time DESC;\",\n  };\n\n  const filterType = reqParams.filterType || \"2\";\n\n  try {\n    let list = await db.query(selects[filterType], {\n      type: QueryTypes.SELECT,\n    });\n\n    list = list.map((item) =\u003e ({\n      ...item,\n      create_time: dayjs(item.create_time)\n        .tz(\"Asia/Shanghai\")\n        .format(\"YYYY-MM-DD HH:mm:ss\"),\n      is_finished: item.is_finished === \"0\" ? false : true,\n    }));\n\n    ctx.body = {\n      code: 200,\n      data: list || [],\n      msg: \"ok\",\n    };\n  } catch (e) {\n    console.log(e);\n  }\n\n  await next();\n});\n\nmodule.exports = todoList;\n```\n\n#### 4.4 Dockerfile\n\n```Dockerfile\nFROM alpine:3.15 AS base\n\nENV NODE_ENV=production \\\n  APP_PATH=/www/node-server\n\nWORKDIR $APP_PATH\n\n# 使用apk命令安装 nodejs\nRUN apk add --no-cache --update nodejs=16.13.2-r0 npm\n\n# 基于基础镜像安装项目依赖\nFROM base AS install\n\n# 将当前目录的package.json 拷贝到工作目录下\nCOPY package.json package-lock.json $APP_PATH/\n\nRUN npm install\n\n# 基于基础镜像进行最终构建\nFROM base\n\n# 拷贝 上面生成的 node_modules 文件夹复制到最终的工作目录下\n# COPY命令复制文件夹的时候，不是直接复制该文件夹，而是将文件夹中的内容复制到目标路径\nCOPY --from=install $APP_PATH/node_modules $APP_PATH/node_modules\n# 拷贝当前目录的文件到工作目录(除了.dockerignore中忽略的)\nCOPY . $APP_PATH/\n\nEXPOSE 4000\n\nCMD [\"npm\", \"run\", \"server\"]\n```\n\n#### 4.5 注意事项\n\n- 数据库连接配置时区`timezone`，设置为东 8 区\n- 设置数据库字符集为`utf8mb4`，方便后面存储表情符\n- `dayjs`库调整显示为东 8 区时间\n- `sequelize`查询出来有重复项(非数据库里的数据重复)，需要配置`type`为`QueryTypes.SELECT`\n\n### 5. MySQL 部分\n\n\u003e 这里选择的版本为**5.7.30**版本，线上 8.0 版本未部署成功\n\n#### 5.1 mysql/Dockerfile\n\n```Dockerfile\nFROM mysql:5.7.30\n\nLABEL version=\"1.0.0\" description=\"todolist MySQL 服务器\"\nWORKDIR /mysql\n\nENV MYSQL_ROOT_PASSWORD=123456\n# MYSQL_DATABASE=todolist\n# MYSQL_ALLOW_EMPTY_PASSWORD=yes\n\n# 启动脚本\nCOPY setup.sh /mysql/setup.sh\n# 创建数据库\nCOPY create_db.sql /mysql/create_db.sql\n# 初始化数据\nCOPY initial_data.sql /mysql/initial_data.sql\n# 设置密码和权限\nCOPY privileges.sql /mysql/privileges.sql\n\nEXPOSE 3306\n\nCMD [\"sh\", \"/mysql/setup.sh\"]\n```\n\n#### 5.2 `mysql/setup.sh`容器启动脚本\n\n```bash\n#!/bin/bash\nset -e\n# https://xie.infoq.cn/article/a3c8ffbd34d818de010f2b0f6\n# 打印mysql服务的状态\necho $(service mysql status)\n\necho '1.启动mysql...'\n#启动mysql\n# service mysql stop\n# service mysql restart\nservice mysql start\n\n# sleep 3\necho '2.创建数据库...'\nmysql \u003c/mysql/create_db.sql\nsleep 3\n\necho '3.开始导入数据...'\nmysql \u003c/mysql/initial_data.sql\n\nsleep 3\necho $(service mysql status)\n\necho '4.修改mysql权限...'\nmysql \u003c/mysql/privileges.sql\nsleep 3\necho '4.权限修改完毕...'\n\n# 防止container启动后退出\n# http://www.mayanpeng.cn/archives/121.html\ntail -f /dev/null\n```\n\n#### 5.3 `mysql/create_db.sql`创建数据库\n\n```sql\nCREATE DATABASE IF NOT EXISTS todolist;\n```\n\n#### 5.4 `mysql/initial_data.sql`初始化表\n\n```sql\n-- 使用todolist库\nUSE todolist;\n\n-- 创建todolist表\nCREATE TABLE IF NOT EXISTS todolist(id VARCHAR(50) PRIMARY KEY, create_time DATETIME UNIQUE, is_finished ENUM('0', '1') DEFAULT '0', msg VARCHAR(100) DEFAULT '--') COMMENT='todolist表' ENGINE=InnoDB DEFAULT CHARSET=utf8;\n\n-- 打印数据库\nSHOW TABLES;\n\n-- 打印表结构\nDESC todolist;\n\n-- 插入1条默认数据\nINSERT INTO todolist(id, create_time, is_finished, msg) VALUES(1, NOW(), '0', 'hello world');\n```\n\n#### 5.5 `mysql/privileges.sql`设置权限密码\n\n```sql\nuse mysql;\nSELECT host, user FROM user;\n\n-- 将数据库的权限授权给root用户，密码为123456\nGRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '123456';\n\n-- 刷新权限这一条命令一定要有：\nflush privileges;\n```\n\n### 6 部署准备\n\n#### 6.1 提前准备好\n\n1. 在`dockerhub`账号或者`阿里云镜像容器服务`创建 3 个仓库，分别放 mysql、koa、vue 应用，我这里创建的是`todolist_mysql`、`koa`和`vite_vue3_pinia`，在下面的 docker-compose.yml 配置文件中一一对应\n   ![dockerhub.jpg](./public/img/dockerhub.jpg)\n\n2. 在你的`github`仓库里里配置好上面 dockerhub 的账号和密码`secrets`，方便于后面使用 github 仓库的 actions 推送。我这里创建的是`DOCKERHUB_TOKEN`放密码，`DOCKERHUB_USERNAME`放账户\n   ![github.png](./public/img/github.png)\n\n#### 6.2 `docker-compose.yml`\n\n```yml\nversion: \"3\"\nservices:\n  mysql: # mysql\n    build: ./mysql\n    # image名称为在dockerhub里创建好的名称\n    image: 你的docker账户名/todolist_mysql:latest\n    container_name: todolist_mysql_server\n    restart: always\n    # MYSQL_ROOT_PASSWORD: \"123456\"\n\n  node: # nodejs服务\n    depends_on:\n      - \"mysql\"\n    build: . # Dockerfile所在目录构建\n    image: 你的docker账户名/koa:latest\n    container_name: koa_server\n    # ports:\n    #   - \"8001:4000\"\n    restart: always # 自动重启\n    environment:\n      - NODE_ENV=production\n    command: npm run server # 覆盖容器启动后默认执行的命令\n\n  vue:\n    depends_on: # vue 容器会保证在 node容器之后启动\n      - \"node\"\n    build: ./web\n    image: 你的docker账户名/vite_vue3_pinia:latest\n    container_name: vite_vue3_pinia\n    restart: always\n    environment:\n      - NODE_ENV=production\n    ports:\n      - \"8001:80\"\n\nnetworks:\n  default:\n    external:\n      name: vue3-koa\n```\n\n#### 6.3 github ci 配置文件\n\n```yml\n# .github/workflows/ci.yml\nname: MySQL + Koa2 Server + Vue3 todolist\n\non:\n  push:\n    branches: [main] # 监听main分支\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v2\n\n      # 制作docker镜像并推送到dockerhub\n      - name: build and push docker image\n        run: |\n          # 这里用到上面创建的secrets变量\n          # 登录你的docker账号\n          docker login -u ${{ secrets.DOCKERHUB_USERNAME }} -p ${{ secrets.DOCKERHUB_TOKEN }}\n          # 进行docker镜像制作\n          docker-compose build\n          # 推送到你的docker账户里\n          docker-compose push\n          # 退出登录\n          docker logout\n```\n\n#### 6.4 提交代码\n\n\u003e 等不及了，是时候进行提交代码了\n\n```bash\ngit add .\n\ngit commit -m \"feat: init\"\n\ngit push -u origin main\n```\n\n不出意外，在仓库的`Actions`里看到构建成功。\n\n![ci.png](./public/img/ci.png)\n\n### 7. `Portainer`出场\n\n\u003e 费了这么大劲，todolist 还没看到，是我的错，最后一步了，别放弃。\n\n#### 7.1 Portainer 的好处\n\n这就如同 git，使用 git GUI 工具和 git bash 命令行其他一样，方便容器管理操作。如果你喜欢命令行，就直接跳过这一节，直接部署容器吧。\n\n把 git 仓库代码下载到服务器，`docker compose up`即可解决战斗！\n\n#### 7.2 登录你的服务器安装[portainer](https://hub.docker.com/r/portainer/portainer)，我这里之前安装的 1.24.2 版本\n\n\u003e 前提是在你的服务器上安装好 docker\n\n1. 下载\n\n```shell\ndocker pull portainer/portainer:latest\n```\n\n2. 运行\n\n\u003e 注意： 这里你的服务器的安全组要开放 9000 端口\n\n```\ndocker run -d -p 9000:9000 -v /var/run/docker.sock:/var/run/docker.sock --restart=always --name prtainer portainer/portainer\n```\n\n#### 7.3 portainer 实操\n\n浏览器里打开`http://你的服务器公网ip:9000`,设置好账户和密码，进行登录，并且设置本地节点\n\n![portainer.png](./public/img/portainer.jpg)\n\n#### 7.3.1 拉取刚才推到 dockerhub 的镜像\n\n\u003e 刚才那 3 个镜像都要拉取\n\n![portainer2.png](./public/img/portainer2.png)\n\n如果是推到阿里云镜像容器的，点击左侧菜单 Registry，然后进去点击 Add registry 添加你的镜像容器 url，同时可以配置`Authentication`,你的账号和密码，然后就可以和上面一样 pull 镜像了\n\n![portainer3.png](./public/img/portainer3.jpg)\n\n#### 7.3.2 设置容器 NetWork，进行容器通信\n\n\u003e 我受不了，到底还有多少步，你骗我。真的马上结束了\n\n实质是使用了 `docker network`，然后再把自己的容器添加到这个 network 中来\n\n```bash\ndocker network create 自定义bridge名称\ndocker network connect 自定义bridge名称 容器名称\n```\n\n这里我们用图形化界面操作\n\n![portainer5.png](./public/img/portainer5.jpg)\n\n#### 7.3.3 部署容器\n\n\u003e 我真的受不了了，还没结束啊，这次真的结束了\n\n点击 container 添加容器。这里必须和 docker-compose 中的顺序一样，先部署 MySQL，再部署 Koa，最后部署 vue 应用\n\n![portainer4.png](./public/img/portainer4.jpg)\n\n进行部署配置，按顺序部署完 3 个容器，容器名称和端口必须都得和配置文件中的一样\n\n![portainer6.png](./public/img/portainer6.jpg)\n\n#### 7.3.4 阿弥陀佛\n\n\u003e xdm，真的结束了，是我折磨你了。一切顺利的话，3 个容器都部署成功\n\n打开浏览器访问你的 todolist, `http://你的ip:8001`\n\n#### 7.3.5 关于保存 emoji 失败\n\n\u003e 因为前面创建表的时候，msg 列没有使用`utf8mb4`字符集。那为什么不在建表的时候就设置好，我错了，xdm，揍我吧\n\n\u003e 觉得复杂难受的，直接跳过\n\n**docker 进入容器实战**\n\n前提是登录你的服务器\n\n1. 查看运行的容器\n\n```shell\ndocker container ls\n```\n\n2. 进入容器\n\n```bash\ndocker exec -it 上面mysql容器的CONTAINER ID sh\n```\n\n3. 连接 mysql\n\n```shell\nmysql -h localhost -u root -p\n# 然后输入设置的密码\n```\n\n不出意外，我们成功进入容器内，并连接上了 mysql\n\n```\n[root@iZbp19ftqv2b85av0b2d4qZ /]# docker exec -it a206f021c205 sh\n# mysql -h localhost -u root -p\nEnter password:\nWelcome to the MySQL monitor.  Commands end with ; or \\g.\nYour MySQL connection id is 53\nServer version: 5.7.30 MySQL Community Server (GPL)\n\nCopyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.\n\nOracle is a registered trademark of Oracle Corporation and/or its\naffiliates. Other names may be trademarks of their respective\nowners.\n\nType 'help;' or '\\h' for help. Type '\\c' to clear the current input statement.\n\nmysql\u003e\n```\n\n4. 修改 todolist 表的 msg 列属性\n\n- 使用 todolist 库\n\n```sql\nUSE todolist;\n```\n\n- 进行修改\n\n```sql\nmysql\u003e ALTER TABLE todolist MODIFY msg VARCHAR(100) character set utf8mb4 collate utf8mb4_unicode_ci default '--';\nQuery OK, 6 rows affected (0.04 sec)\nRecords: 6  Duplicates: 0  Warnings: 0\n```\n\n- 验证是否修改成功\n\n\u003e 可以看到，msg 列已经修改成功了\n\n```sql\nmysql\u003e SHOW CREATE TABLE todolist\\G\n*************************** 1. row ***************************\n       Table: todolist\nCreate Table: CREATE TABLE `todolist` (\n  `id` varchar(50) NOT NULL,\n  `create_time` datetime DEFAULT NULL,\n  `is_finished` enum('0','1') DEFAULT '0',\n  `msg` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '--',\n  PRIMARY KEY (`id`),\n  UNIQUE KEY `create_time` (`create_time`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='todolist表'\n1 row in set (0.00 sec)\n```\n\n- 浏览器里输入 emoji 表情符添加测试，不出意外，已经 ok。结束了！！！\n\n### 8. 参考资料\n\n1. [使用 Portainer 部署 Docker 容器实践](https://segmentfault.com/a/1190000039803577)\n\n2. [MySql 的 Dockerfile 编写](https://xie.infoq.cn/article/a3c8ffbd34d818de010f2b0f6)\n\n3. [Pinia 文档](https://pinia.esm.dev/introduction.html#a-more-realistic-example)\n\n4. [sequelize 中文文档](https://github.com/demopark/sequelize-docs-Zh-CN)\n\n5. [dayjs](https://dayjs.gitee.io/zh-CN/)\n\n6. [【实战】Node 服务中如何写日志？](https://juejin.cn/post/7045999468843368462)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrect%2Fdocker-compose","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsrect%2Fdocker-compose","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsrect%2Fdocker-compose/lists"}