node.jsdockerexpressdockerfile

Run NodeJs inside Docker-in-Docker container


I have a simple express.js server that, for some operations, needs to spawn some docker containers. I want to dockerize this NodeJs server using a Dockerfile, so that it will spawn docker containers inside a docker container. My Dockerfile currently looks like this:

FROM docker:23.0.6-dind-alpine3.18


RUN apk update
RUN apk add --no-cache nodejs 
RUN apk add --no-cache npm
RUN apk add --no-cache iptables bash
RUN apk add --no-cache --upgrade bash

WORKDIR /usr/src/app

COPY package*.json ./
RUN npm install --force --verbose


COPY . .


RUN npm run completeBuild


EXPOSE 4000

CMD ["npm","start"]

I build it with:

docker build -t trydocker .

And I run it with:

docker run --privileged -it -p 4000:4000 -p 2375:2375 -p 2376:2376 trydocker 

The server process gets spawned correctly and I can access my express server via localhost:4000. But when I try to call docker pull from inside the container I get an error that says that my docker daemon is not running.

Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?

Instead, if I remove the last directive CMD ["npm","start"] from the Dockerfile, the docker daemon starts correctly, but my server is not online!

Is there a way to have both running?

I have looked at some related issues, like this one, but it did not help.


Solution

  • A Docker container only runs one process. Your image runs the Node application (its CMD runs npm start) but because of this it does not run the nested Docker daemon.

    If you really need Docker-in-Docker (do you?) then you need to launch the nested Docker daemon as a sibling container. This Docker Compose setup could do it, for example:

    version: '3.8'
    services:
      docker:
        image: docker:24-dind
        privileged: true
        environment:
          DOCKER_TLS_CERTDIR: /certs
        volumes:
          - docker-certs-ca:/certs/ca
          - docker-certs-client:/certs/client
          - docker-data:/var/lib/docker
      app:
        build: .
        environment:
          DOCKER_HOST: tcp://docker:2376
          DOCKER_TLS_CERTDIR: /certs
        volumes:
          - docker-certs-client:/certs/client
    volumes:
      docker-certs-ca:
      docker-certs-client:
      docker-data:
    

    The Docker Hub docker image page has some more details on the settings.

    Your application image itself doesn't embed the Docker daemon and so it can use a more normal node image base

    FROM node:lts
    # exactly the last half of your existing Dockerfile
    WORKDIR /usr/src/app
    
    COPY package*.json ./
    RUN npm install --force --verbose
    
    COPY . .
    RUN npm run completeBuild
    
    EXPOSE 4000
    CMD ["npm","start"]
    

    Using Docker-in-Docker is usually discouraged, since there are many complexities around which Docker daemon you're actually using and the DinD container must run in privileged mode. With this same Dockerfile, you could remove the docker container and associated volumes, and instead bind-mount the host's /var/lib/docker.sock socket file into the container on the same path, which would allow you to launch containers on the host's Docker daemon.