In this example we are going to create and run Docker images.
We will start from 02-github-actions
.
npm install
to install previous sample packages:
npm install
- We can create our custom images. In this case, we will use the node image, the alpine version as base image to create our custom one:
./Dockerfile
FROM node:14-alpine
You can use Docker VSCode extension
- Let's create the path where we are going to copy our app:
./Dockerfile
FROM node:14-alpine
+ RUN mkdir -p /usr/app
+ WORKDIR /usr/app
RUN: run commands inside container WORKDIR: all commands after that will be executed in this path
- Let's add the
.dockerignore
to avoid unnecessary files:
./.dockerignore
node_modules
.vscode
dist
.editorconfig
.env
.env.example
.gitignore
.github
package-lock.json
README.md
- Copy all files:
./Dockerfile
FROM node:14-alpine
RUN mkdir -p /usr/app
WORKDIR /usr/app
+ COPY ./ ./
- Execute install and build:
./Dockerfile
FROM node:14-alpine
RUN mkdir -p /usr/app
WORKDIR /usr/app
COPY ./ ./
+ RUN npm install
+ RUN npm run build
- How we can run this image? We need to
build
our custom image before run it to be accesible by a docker container.
docker build -t my-app:1 .
-t: Give a name to image. We can use
-t name:tag
- How to run this image? Right now, we haven't a web server to resolve this static files, so we can use the interactive mode to see inside container:
docker images
docker run --name my-app -it my-app:1 sh
> ls
> exit
docker container rm my-app
Tag is optionally. We can see the files after build in
cd ./dist && ls
- We can create some docker steps to install server and execute it:
./Dockerfile
FROM node:14-alpine
RUN mkdir -p /usr/app
WORKDIR /usr/app
COPY ./ ./
RUN npm install
RUN npm run build
+ RUN cp -r ./dist ./server/public
+ RUN cd server
+ RUN npm install
+ ENTRYPOINT [ "node", "server" ]
RUN vs ENTRYPOINT: I don't want to run
node server
when we build the image, we want to run it when run the container.
- Run the container:
docker build -t my-app:1 .
docker images
It creates a image due to replace same tag. We can remove it with
docker image prune
- Run new container:
docker run --name my-app my-app:1
// In another terminal
docker exec -it my-app sh
> ls
> exit
-
Try to access
http://localhost:8081
-
Why can't we access to
http://localhost:8081
? Because this process is executing itself inside container, we need to expose to our machine:
docker container stop my-app
docker container rm my-app
./Dockerfile
...
+ ENV PORT=8083
+ EXPOSE 8083
ENTRYPOINT [ "node", "server" ]
- Run it:
docker build -t my-app:1 .
docker run --name my-app --rm -d -p 8080:8083 my-app:1
-p: Expose a port or a range of ports
--rm: Automatically remove the container when it exits. We still have to use
docker stop
.
-d
: To start a container in detached mode
-
Open
http://localhost:8080
-
If we check
docker images
we can see dangling images, due to use same tags for each build.
docker images
docker image prune
docker images
- On the other hand, we have an image with
384MB
, too much size isn't it?. We should use multi-stage builds to decrease this size, with only the necessary info:
./Dockerfile
- FROM node:14-alpine
+ FROM node:14-alpine AS base
RUN mkdir -p /usr/app
WORKDIR /usr/app
+ # Prepare static files
+ FROM base AS build-front
COPY ./ ./
RUN npm install
RUN npm run build
- RUN cp -a ./dist/. ./server/public
- RUN cd server
- RUN npm install
+ # Release
+ FROM base AS release
+ COPY --from=build-front /usr/app/dist ./public
+ COPY ./server/package.json ./
+ COPY ./server/index.js ./
+ RUN npm install --only=production
ENV PORT=8083
EXPOSE 8083
- ENTRYPOINT [ "node", "server" ]
+ ENTRYPOINT [ "node", "index" ]
We could use
npm ci
instead ofnpm install
if we have apackage-lock.json
generated.
- Run it:
docker build -t my-app:2 .
docker images
docker stop my-app
docker run --name my-app --rm -d -p 8080:8083 my-app:2
We can add more env variables, for example feed the public
folder.
- Update server to consume env variable:
./server/index.js
const express = require('express');
const path = require('path');
const app = express();
- const staticFilesPath = path.resolve(__dirname, '../dist');
+ const staticFilesPath = path.resolve(__dirname, process.env.STATIC_FILES_PATH);
app.use('/', express.static(staticFilesPath));
const PORT = process.env.PORT || 8081;
app.listen(PORT, () => {
console.log(`App running on http://localhost:${PORT}`);
});
- Update Dockerfile:
./Dockerfile
...
# Release
FROM base AS release
+ ENV STATIC_FILES_PATH=./public
- COPY --from=build-front /usr/app/dist ./public
+ COPY --from=build-front /usr/app/dist $STATIC_FILES_PATH
COPY ./server/package.json ./
...
We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
Basefactor, consultancy by Lemoncode provides consultancy and coaching services.
Lemoncode provides training services.
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend