Yordis Prieto Logo
Back to TIL

Create a CLI Docker image for a package

nodejsdockerci

I had to create a Docker Image for a CLI package. I learned how to make it friendly to use with plain docker run and as well as add some nice things that makes it friendly to use in GitLab.

Files

I was using a custom NPM registry, and I needed to be able to pull packages for @core scope, so I had to create a <rootDir>/.npmrc file.

@core:registry=https://gitlab.example.com/api/v4/packages/npm/
//gitlab.example.com/api/v4/projects/1123/packages/npm/:_authToken=${GITLAB_TOKEN}
//gitlab.example.com/api/v4/packages/npm/:_authToken=${GITLAB_TOKEN}

Notice GITLAB_TOKEN being injected in the .npmrc, therefore, we will need such environment variable when we install the packages otherwise it will fail.

The <rootDir>/package.json file:

{
  "bin": {
    "my-cli-name": "bin/my-cli-name",
  },
}

And finally the <rootDir>/Dockerfile file:

# The deps stage only deals with installing packages
FROM node:lts AS deps
# Inject the token used in the `.npmrc` only during the installation, this is
# really good, that means we don't leak the token in the final image, this is
# extremely important for security reasons
ARG GITLAB_TOKEN
WORKDIR /app
COPY package.json yarn.lock .npmrc ./
RUN yarn install --frozen-lockfile

# The builder stage deals with building the package
FROM node:lts AS builder
ARG GITLAB_TOKEN
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
# Run the script that builds the code, like compiling with TypeScript
RUN yarn build
# Clean up the `node_modules` directory
RUN yarn install --production --ignore-scripts --prefer-offline

# The runner stage copy the necessary files for production usage and prepare
# the image to be CLI friendly image.
FROM node:lts AS runner
WORKDIR /my-package-name
COPY --from=builder /app/package.json .
COPY --from=builder /app/bin ./bin
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
# This is a trick, `yarn link` will symlink the package as a global install,
# therefore, all the bin registered in the package.json will be available
# globally
RUN yarn link

# If you want to avoid having to type the executable name when running the
# container. Also notice that I use the name of the bin as if it was installed
# globally
ENTRYPOINT ["my-cli-name"]

Using the image with docker

First, build the image

# Notice that we pass the GITLAB_TOKEN
docker build -t my-cli-image:latest --build-arg GITLAB_TOKEN .

Now, we can use the image as a CLI:

docker run --rm my-cli-image:latest --help

Using the image in GitLab CI

Since we used yarn link, all the bins from the packages are available globally:

shortcut:release:
  image: my-cli-image:latest
  script:
    - my-cli-name --help