Docker容器连接终端时自动运行交互式菜单的实现问询
问题描述
我正在创建用于环境配置的Docker镜像,需求是当用户传入--interactive参数时,启动容器后直接显示交互式菜单,提供「Run Full Setup」「Create Database」「Create Database User」等选项供选择。目前我仅能通过docker exec -it setup node /app/interactive.js命令手动执行脚本才能唤起菜单,尝试docker-compose up --attach setup、docker-compose up等命令均无法实现预期效果,希望找到无需额外输入命令,连接容器终端即可自动运行交互式菜单的方案。
相关代码文件
Dockerfile
FROM node WORKDIR /app COPY ./package.json . COPY ./interactive.js . WORKDIR /app/modules COPY ./modules . WORKDIR /app RUN apt update && apt install -y curl jq iputils-ping traceroute lsb-release && \ npm install && \ ln -sf /bin/bash /bin/sh ENTRYPOINT ["node","/app/modules/main.js"] CMD [ "$*" ]
docker-compose.yml
version: "3.9" services: setup: image: setup build: context: . dockerfile: Dockerfile command: --interactive volumes: - ./interactive.js:/app/interactive.js - ./package.json:/app/package.json - ./modules:/app/modules stdin_open: true tty: true
modules
interactiveMenu.js
function createAndStartMenu(){ const menu = require('node-menu'); const setup = require('/app/modules/setup.js'); menu.addDelimiter('-', 40, 'Setup') menu.addItem('Run Full Setup',setup.runFullSetup); menu.addItem('Create Database',setup.createDatabase); menu.addItem('Create Database User',setup.createDatabaseUser); menu.addItem('Stop Setup',async ()=>{ menu.stop(); }); menu.addItem('Finish Interactive Setup Successfully', async ()=>{ console.log("Finishing interactive setup..."); console.log("The application will shutdown with exit code 0, it may take a few seconds...") process.exit(0); }) menu.addItem('Finish Interactive Setup With Errors', async ()=>{ console.log("Finishing interactive setup with errors..."); console.log("The application will shutdown with exit code 1, it may take a few seconds...") process.exit(1); } ); menu.start(); } module.exports = { createAndStartMenu }
main.js
async function runInteractiveSetupUserNeedsToConnectToContainer(){ console.log("Running interactive setup..."); console.log("To exit press CTRL+C"); console.log("To cancel press CTRL+C twice"); console.log("As you started the application in interactive mode, you may need to take some actions...") console.log("You can attach to the running container with the following command:") console.log("docker exec -it <container_name> /bin/bash") console.log("Or you can connect throught the docker integrated terminal, if so you may need to call the following command:") console.log("node interactive.js") let cancellationToken =false; process.on('SIGINT', function () { console.log("Cancellation token set to true throught SIGINT...") cancellationToken = true; }); setInterval(()=>{ if(cancellationToken) return process.exit(0); console.log("Waiting for cancellation token..."); },1000); } async function runInteractiveSetupWithoutNeedToConnectToContainer(){ console.log("Running interactive setup..."); const interactiveMenu = require('/app/modules/interactiveMenu.js'); interactiveMenu.createAndStartMenu(); } async function runNonInteractiveSetup(){ console.log("Running non-interactive setup..."); const setup = require('/app/modules/setup.js'); await setup.runFullSetup(); } async function run(){ const commandLineArgs = require('command-line-args') const optionsDefinitions=[ {name:'interactive',alias:"i", type:Boolean,defaultOption:false} ]; const options = commandLineArgs(optionsDefinitions) const interactive = options.interactive; if(interactive) await runInteractiveSetupUserNeedsToConnectToContainer(); else await runNonInteractiveSetup(); } run().then(()=>console.log("Setup finished with no errors...")).catch((err)=>console.log(`Setup finished with errors: ${err}`));
setup.js
async function runFullSetup(){ await createDatabase(); await createDatabaseUser(); } async function createDatabase(){ console.log('creating database'); }; async function createDatabaseUser(){ console.log('creating user'); }; module.exports= { runFullSetup, createDatabase, createDatabaseUser }
interactive.js
const { createAndStartMenu } = require('/app/modules/interactiveMenu.js'); createAndStartMenu();
package.json
{ "dependencies": { "axios": "^1.4.0", "command-line-args": "^5.2.1", "install": "^0.13.0", "mongodb": "^5.7.0", "mysql": "^2.18.1", "node-menu": "^1.3.2", "npm": "^9.8.0", "pg": "^8.11.1" } }
解决方案
问题根源
当前main.js中,检测到--interactive参数时执行的是runInteractiveSetupUserNeedsToConnectToContainer函数,该函数仅打印提示并循环等待,未直接启动交互式菜单。正确逻辑应调用runInteractiveSetupWithoutNeedToConnectToContainer函数直接唤起菜单。
修复步骤
- 修改main.js的分支逻辑
将run函数中的交互式分支替换为正确的菜单启动函数:
async function run(){ const commandLineArgs = require('command-line-args') const optionsDefinitions=[ {name:'interactive',alias:"i", type:Boolean,defaultOption:false} ]; const options = commandLineArgs(optionsDefinitions) const interactive = options.interactive; if(interactive) await runInteractiveSetupWithoutNeedToConnectToContainer(); // 替换此处的函数调用 else await runNonInteractiveSetup(); }
验证容器终端配置
你的docker-compose.yml已正确设置stdin_open: true和tty: true,这两个参数是容器支持交互式输入的核心,无需修改。重新构建并启动容器
执行以下命令更新镜像并启动:
docker-compose build setup docker-compose up setup
额外优化
- 移除
Dockerfile中ln -sf /bin/bash /bin/sh末尾多余的]符号,避免构建失败 - 可将
interactive.js的逻辑整合到main.js中,减少冗余文件
内容的提问来源于stack exchange,提问作者Vinicius Andrade
相关产品推荐
相关产品推荐

