[Day 3] JS in Pipeline (3): Docker and Local Development Environment (3)


The goal of this series is to introduce some best practices in the local development environment and create a CI/CD pipeline for NodeJS applications.

GitHub repo for this article series: https://github.com/jeanycyang/js-in-pipeline

In this article, we are going to use Docker Compose in our local development environment.


First Glance at Docker Compose

So now we can start our gift code server as a Docker Container.

$ docker run -it -p 40000:3000 --name myapp -v `pwd`:`pwd` -w `pwd` giftcodeserver npm run dev

Maybe we can write it in our README.md. And copy and paste it in the terminal every time we need it. (Of course, you can use alias!)

Is there any other way to start a Docker Container — Could it be just like Dockerfile, very useful and declarative? Yes! It is called Docker Compose.

Let’s create a docker-compose.yaml file in the project directory (.yml extension also works).

# ${PROJECT_DIR}/docker-compose.yaml
# only for development
version: '3'
services:
  server:
    build:
      context: ./
    ports:
      - "40000:3000"
    volumes:
      - ./:/usr/src/app
    command: ["npm", "run", "dev"]

https://github.com/jeanycyang/js-in-pipeline/blob/3acbfdf20f514165f33e3d7e82ae6d5ad29821fc/docker-compose.yaml

It is almost the same as our docker-run command.

This app now is named server. You can call it whatever you want, e.g. giftcodeserver, nodeserver, api_server.

build context tells Docker Compose to build this app’s Docker image and the Dockerfile locates in ./. It works the same as context: ./Dockerfile.

volumes section tells Docker Compose to mount ./ (host) to /usr/src/app (container).

And command works the same as our docker-run command. It overrides the CMD instruction in our Dockerfile. When the container starts, it will run npm run dev instead of node index.js.

Run docker-compose up in the project directory and you shall see Docker Compose take care of everything for you! To stop it, press Ctrl+C in the terminal.

Obviously, Docker Compose can do much more than just running one Container. Docker Compose’s killer feature is, it can start multiple Containers as described in the docker-compose.yaml file at once; moreover, you can instruct the relationships between containers.


Run Multiple Containers!

We use MySQL as our database. It’s time to get rid of “google “MySQL”; click MySQL’s website; find the download page; download the installer; install MySQL; set up everything and try to start it” process!

Let’s take a look at MySQL’s official Docker images: https://hub.docker.com/_/mysql.

We will use MySQL 5.7.

# ${PROJECT_DIR}/docker-compose.yaml
# only for development
version: '3'
services:
  server:
    ....
  db:
   image: mysql:5.7

Name our MySQL Container db and use the image tagged 5.7.

The Environment Variables section on the page tells us that we can set up MySQL using environment variables.

db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: V4TynEq8RsfvyW7JxXGUpjD9MSrbaB9W
      MYSQL_USER: giftcodeserver
      MYSQL_PASSWORD: m5kCtWQ2G8Zr9VxD79R8LMCfmzX6SAWZ
      MYSQL_DATABASE: giftcodeserver
    ports:
      - "3306:3306"

Now we have set up our MySQL.

Run docker-compose up in the project directory. You will see Docker Compose start two Containers for us! Later, if necessary, you can add more Containers — be that Redis, RabbitMQ, MongoDB… whatever you need!

So far so goo…What? Wait! A new problem shows up — our server Container can no longer connect to the db Container!

// Sequelize is an ORM package, we will use it in this series
const sequelize = new Sequelize('giftcodeserver', 'giftcodeserver', 'm5kCtWQ2G8Zr9VxD79R8LMCfmzX6SAWZ', {
  host: 'localhost',
  dialect: 'mysql'
});

It is because the host is no longer “localhost”. Our gift code server is started by Docker Compose, runs as a Docker Container in its own network.

As stated in the documentation, Links allow you to define extra aliases by which a service is reachable from another service. They are not required to enable services to communicate — by default, any service can reach any other service at that service’s name.

Changing host property to db makes our server work again.

const sequelize = new Sequelize('giftcodeserver', 'giftcodeserver', 'm5kCtWQ2G8Zr9VxD79R8LMCfmzX6SAWZ', {
  host: 'db', // the service name set in docker-compose.yaml
  dialect: 'mysql'
});

Nonetheless, explicitly describing it in docker-compose.yaml is still a best practice.

server:
  build:
    context: ./
  ......
  links:
     - db

Last but not least, we can add one more property — depends_on.

Generally speaking, starting a database instance and getting it ready takes more time than starting an API server. We can use depends_on to control the startup order.

server:
  build:
    context: ./
  ......
  links:
     - db
  depends_on:
     - db

Stop Docker Compose by Crtl+C then run docker-compose up again.

Now we see the power of Docker Compose and how great it is for our local development environment!

One More Thing — Persistent Data

You might notice, every time we stop Docker Compose, the data in database will also be removed as the Container dies. If you wish to persist it, you can use Docker Volume again — mount a directory in your local machine (host) to the Container so that your data will be persisted in the host.

The “Where to Store Data” section tells us which directory to mount.

db:
  image: mysql:5.7
  ports:
    - "3306:3306"
  volumes:
    - ./mysql-data:/var/lib/mysql

You will see a directory named mysql-data being created and MySQL data will be stored in this directory. Let’s add mysql-data to .gitignore.

Next time you restart your container using Docker Compose, the data will still be there unless you delete the mysql-data folder in your machine.

Done!


Useful Links/References

#devops #docker #docker compose #nodejs #MySQL





JS in Pipeline
The goal of this series is to introduce some best practices in the local development environment and create a CI/CD pipeline for NodeJS appl

留言討論