{"id":25741219,"url":"https://github.com/charlie5dh/docker-node","last_synced_at":"2026-05-04T10:35:39.570Z","repository":{"id":132500778,"uuid":"476001467","full_name":"Charlie5DH/docker-node","owner":"Charlie5DH","description":"Simple NodeJS Docker Application. A tutorial to Docker using Node and MongoDB.","archived":false,"fork":false,"pushed_at":"2022-03-30T18:49:43.000Z","size":246,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-26T09:33:19.907Z","etag":null,"topics":["docker","docker-compose","javascript","nodejs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Charlie5DH.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-30T18:20:29.000Z","updated_at":"2022-03-30T21:44:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"e1c3cb80-ee78-40a1-ba4b-ce945102319e","html_url":"https://github.com/Charlie5DH/docker-node","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Charlie5DH/docker-node","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Charlie5DH%2Fdocker-node","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Charlie5DH%2Fdocker-node/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Charlie5DH%2Fdocker-node/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Charlie5DH%2Fdocker-node/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Charlie5DH","download_url":"https://codeload.github.com/Charlie5DH/docker-node/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Charlie5DH%2Fdocker-node/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261733871,"owners_count":23201741,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["docker","docker-compose","javascript","nodejs"],"created_at":"2025-02-26T09:27:25.411Z","updated_at":"2026-05-04T10:35:39.541Z","avatar_url":"https://github.com/Charlie5DH.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Simple NodeJS Docker Application. A tutorial to Docker using Node and MongoDB.\n\nThis is a simple app to test and familiarize with docker following the step in the [freecodecamp.org](https://www.youtube.com/watch?v=9zUHg7xjIqQ\u0026t=1862s) tutorial. In this app we create two containers, one with a Node app and other with a MongoDB database, which are connected and with functional APIs.\n\n### Steps\n\n- Create a simple node app\n- - `npm init`\n- - `npm install express`\n- - Create `index.js`\n\nThis creates a `node_modules` folder.\n\n### Docker initial content\n\n```docker\n# take the official node16 image\nFROM node:16\n\n# Create app directory\nWORKDIR /app\n# Install app dependencies\n# A wildcard is used to ensure both package.json AND package-lock.json are copied\n# where available (npm@5+)\nCOPY package*.json .\n\n# If you are building your code for production\n# RUN npm ci --only=production\nRUN npm install\n\n# Bundle app source\nCOPY . ./\n\n# Port to send traffic on the container.\n# Application (Node APP) listening on port 3000.\nEXPOSE 3000\nCMD [\"node\", \"index.js\"]\n```\n\n### Docker commands\n\n- Create the docker image. This creates an imge named crmorales/docker-node-testing with version 1.0\n\n        `docker build . -t crmorales/docker-node-testing:1.0`\n\n- Run the image as a docker container. For this, we have to specify the docker port and the application ports (`-p`), the `name` of the container image and the name of the running container `(--name)`\n\n        `docker run -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\n- Remove running container\n\n        docker rm /node-app-testing -f\n\n- Show docker process\n\n        docker ps\n\n- Look at the content inside the docker container. Once in the we can run an `ls` to see the content. We can see that the docekr container is basically our app, the same way we have it configure in our code editor (our pc)\n\n        docker exec -it node-app-testing bash\n\n- Insect logs for errors\n\n        docker logs \u003capp_name\u003e\n\n- Show stopped and running containers\n\n        docker ps -a\n\n### Change Dockerfile to update as code updates.\n\nWith the current config, the Dockerfile is configured to run a stalled code, this means, the Docker container runs the code that is built in the image, but does update with new changes. This is not good because we will have to kill the container and rebuild it every time we want to see the changes we made, this will slow downnnnnnn our developing time.\n\nWorkaround:\n\nIn Docker we have volumens. One of these volumes allows us to sync a folder or file system to a folder in our docker container. To do this we have to re run ou container specifying the volume `v`.\n\n`docker run -v path_to_folder_in_local_machine:path_to_folder_in_docker_container -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\nhard coded would be:\n\n`docker run -v D:\\Web Development\\docker-testing\\:/app -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\nThis looks a litle messy, so we can use variables.\n\n- `docker run -v %cd%:/app -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n- `docker run -v ${pwd}:/app -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0` for **pwsh**\n\nThis should work, but we have to do another thing in our local development, which install `nodemon`. Nodemon allows us to see changes in node during development, without having to restart our server every time we make a change\n\n- `npm install nodemon --save-dev` as a de dependency\n\nand then add it to the `package.json` in the scripts:\n\n```json\n\"scripts\": {\n   \"start\": \"node index.js\",\n   \"dev\": \"nodemon -L index.js\"\n },\n```\n\nnow, we are not going to run `node index.js` anymore, instead, we are running `npm run dev`, so we have to change this in our Dockerfile\n\n```docker\nCMD [\"npm\", \"run\", \"dev\"]\n```\n\nAt this point, we should be able to see changes in real time in our development. **Note that we are developing in our docker container and not in our local env.** So if we remove the `node_modules` local to our PC, we should not have any problems, however, we have to be carefull because we are using the `-v` flag, because this will delete the `node_modules` in our Docker Container.\n\nTo fix this, we can create a new volume to specify we don not want to remove the `node_modules` in our Docker container.\n\n- `docker run -v ${pwd}:/app -v /app/node_modules -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\n#### Make Docker Container as read only\n\n- `docker run -v ${pwd}:/app:ro -v /app/node_modules -p 3000:3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\n### Enviromental variables in our Docker Container\n\nWe have to add the enviromental variable to the Docker file as `ENV PORT \u003cvalue\u003e`. Then pass the port in the run command\n\n- `docker run -v ${pwd}:/app:ro -v /app/node_modules -p 3000:3000 --env PORT=3000 -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\nNow, if we havemany enviromental variables, it get messy passing each variable through the console using the `--env VAR \u003cvalue\u003e` flag. We can instead create a file named .env and pass the file\n\n- `docker run -v ${pwd}:/app:ro -v /app/node_modules -p 3000:3000 --env-file ./.env -d --name node-app-testing crmorales/docker-node-testing:1.0`\n\n### Cleaning volumens\n\n- `docker volume prune`\n- `docker rm -fv node-app-testing`\n\n## Docker Compose\n\nAt this point, we have to run a super long command, this is not cool, it is propense to errors, so, we are going to use a feature called docker compose.\n\n```docker\nversion: \"3.9\"\nservices:\n  docker-node-app:\n    build: .\n    ports:\n      - \"3000:3000\"\n    volumes:\n      - ./:/app\n      - /app/node_modules\n    #environment:\n    #  - PORT=3000\n    env_file:\n      - ./.env\n```\n\n- The `version` is the docker version you want.\n- `services` start describing the services you want to run (the containers)\n- `docker-node-app` is the name of the app to build\n- `build .` means build everything in the current directory\n- `ports` describes the ports using `(localhost:container)`\n- `volumes` is for the sync between folders, but in this case we do not have to use `${pwd}` to get the path, we can just use `./` to refer to the current folder. `./:/app` means sync this current dir to the /app one in the docker container. Also, the `/app/node_modules` is for ignore the `node_modules` folder, because we build it inside the container.\n\nThen we have to run `docker-compose up -d` and to shut it down `docker-compose down -v`\n\nWhen you make a change to the Dockerfile, docker compose needs to rebuild the image, but it does not do it by itself, we have to specify it with:\n\n- `docker-compose up -d --build`. This forces a new build. This is only needed if we make changes to the image (Dockerfile)\n\n## Separate set of commands for development and for production.\n\nWe can create separate Dockerfiles for development and for production. In production we don't want the sycn between folders, so we don't need the volumens, and there will be other parameters we have in development we don't want in prod, for example, in production we can run directly `npm start` or `node index.js` to start the app, in development we are using `npm run dev` to use `nodemon` and see changes happening.\n\nSo, we create a file named `docker-compose.dev.yml` for development, another named `docker-compose.prod.yml` and a common one, which is the `docker-compose.yml` file. The `docker-compose` file has the configuration that is common for both dev and prod.\n\n```yml\n# docker-compose.yml\nversion: \"3.9\"\nservices:\n  docker-node-app:\n    build: .\n    ports:\n      - \"3000:3000\"\n    environment:\n      - PORT=3000\n```\n\nThe `docker-compose.dev.yml` has the configuration for dev.\n\n```yml\nversion: \"3.9\"\nservices:\n  docker-node-app:\n    build:\n      context: .\n      args:\n        NODE_ENV: development\n    volumes:\n      - \"./:/app\"\n      - \"/app/node_modules\"\n    environment:\n      - NODE_ENV=development\n    command: npm run dev\n```\n\nAnd the `docker-compose.prod.yml` the configuration for production.\n\n```yml\nversion: \"3.9\"\nservices:\n  docker-node-app:\n    build:\n      context: .\n      args:\n        NODE_ENV: production\n    environment:\n      - NODE_ENV=production\n    command: npm start\n```\n\nIn `docker-compose.dev.yml` and `docker-compose.prod.yml` we specify the build. In the build parameter, `context` is the location, which is still `.`, and `args` are the arguments to pass to the Dockerfile. We do this because we are expecting this parameter in the Dockerfile to determine the enviroment (development or production) to run the correct command (`npm run dev` or `npm start`).\n\n```docker\nFROM node:16\nWORKDIR /app\nCOPY package*.json .\nRUN npm install\n\n#Prepare our image to run different commands depending on the enviroment\nARG NODE_ENV\nRUN if [ \"$NODE_ENV\" = \"development\" ]; \\\n        then npm install; \\\n        else npm install --only=production; \\\n        fi\n\nCOPY . ./\nENV PORT 3000\nEXPOSE $PORT\nCMD [\"npm\", \"start\"]\n```\n\nTo run this, we can use the same `docker-compose up` command with a few tweaks. We have to pass the files to the command in order.\n\n- `docker-compose up -f docker-compose.yml -f docker-compose.dev.yml -d`\n- `docker-compose up -f docker-compose.yml -f docker-compose.prod.yml -d --build`. We need to specify `build` beacuse we removed the sync for production.\n- `docker-compose down -f docker-compose.yml -f docker-compose.dev.yml -v`\n\n## Adding another service\n\nWe are going to add another service now, specifically a mongoDB database. For this, we will use the original MongoDb image in DockerHub. We can specify this in the docker compose file. We specify the username and password of the database as enviromental variables\n\n```yml\nmongo:\n  image: mongo # we are using the offical mongo image from docker hub\n  environment:\n    - MONGO_INITDB_ROOT_USERNAME=carlos\n    - MONGO_INITDB_ROOT_PASSWORD=thepasswordis\n```\n\nNow, when we run the service `docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d` we can see a new service with the name of the folder and mongo after the name of the folder. **Note** we don't need to rebuild because the **Dockerfile** has not changed, also, this will pull the Mongo image from dockerhub.\n\nPulling the MongoDB image means that an instance of MongoDb will be installed in our docker container.\n\nWe can inspect the new service by typing `docker exec -it docker-testing-mongo-1 bash`.\n\nAnd inside the service (Container) we can connect to our database by typing `mongo -u \"carlos\" -p \"thepasswordis\"`. `-u` for username and `-p` for password.\n\nWe can create a new database with the `use` comamand, `use mydb` and play a little with the database\n\n```mongo\nuse mydb\nshow dbs\ndb.books.insert({\"name\": \"Harry Potter\"})\ndb.books.find()\n```\n\nAlternatively we can just run `docker exec -it docker-testing-mongo-1 mongo -u \"carlos\" -p \"thepasswordis\"` and we will enter the database.\n\n**NOTE:** Once we do a `docker-compose down` we will lose our database. This is a problem for our production enviroment. To solve this we use volumens.\nAdd the following line.\n\n```yml\nmongo:\n  image: mongo # we are using the offical mongo image from docker hub\n  environment:\n    - MONGO_INITDB_ROOT_USERNAME=carlos\n    - MONGO_INITDB_ROOT_PASSWORD=thepasswordis\n  volumes:\n    - mongo-db:/data/db\n\n# declare the volumens\nvolumes:\n  mongo-db:\n```\n\nIn this case, we can't run `docker-compose -f docker-compose.yml -f docker-compose.dev.yml down -v` to kill our service because this will delete our mongo-db volume. **NOTE:** Can't run `-v` flag. So our anonymous volumes will accumulate and we have to remove them mannually by running `docker volume prune`.\n\nTo connect to the database we need the connection url. To see the IP address of our container we can run `docker inspect \u003capp_name\u003e` and search for network.\n\n`mongodb://carlos:thepasswordis@192.168.0.2:27017/?authSource=admin`\n\n```js\nconst CONNECTION_URL =\n  \"mongodb://carlos:thepasswordis@192.168.0.2:27017/?authSource=admin\";\n\nmongoose\n  .connect(CONNECTION_URL, { useNewUrlParser: true, useUnifiedTopology: true })\n  .then(() =\u003e\n    app.listen(PORT, () =\u003e\n      console.log(`Server Running on Port: http://localhost:${PORT}`)\n    )\n  )\n  .catch((error) =\u003e console.log(`${error} did not connect`));\n```\n\nIf we do a `docker network ls` we can see some networks. We can see the `bridge` and `host` networks, which are the defaults created with docker, and we can see another named `docker-testing_default`, this is the custom one created for our application. When we have a custom network, we have a DNS service, so, if we want to talk between containers, we can use the names of the containers (the names declared in the docker-compose file or service names)\n\n```bash\nPS D:\\Web Development\\docker-testing\u003e docker network ls\nNETWORK ID     NAME                     DRIVER    SCOPE\na3b9c3e14852   bridge                   bridge    local\nd02dedc56f2b   docker-testing_default   bridge    local\n967174efa7dd   host                     host      local\ndecec2ce90d7   none                     null      local\n```\n\nSo now, we can use the DNS, insted of the IP address 192.168.0.2:\n\n`\"mongodb://carlos:thepasswordis@mongo:27017/?authSource=admin\"`\n\nWe can check this is working by entering in the app `docker-testing-docker-node-app-1` and doing a `ping mongo`.\n\n## Dependencies between components\n\n`depends_on` can be used in the docker-file to specify which container should be runned first, in case one depends on the other.\n\n### Connect using MongoDB Compass\n\n`mongodb://\u003cusername\u003e:\u003cpassword\u003e@localhost:\u003cport\u003e/?authSource=admin`\n\n`mongodb://carlos:thepasswordis@localhost:27017/?authSource=admin`\n\nWe have to discover the ports in the `docker-compose.dev.yml`. We do not do this in production because we don't need to connect with other app to the database.\n\n## Adding load balancer NGINX\n\n\u003cimg src=\"./assets/Nginx load balance.png\" /\u003e\n\n**NOTE**: For production we have to configure the enviromental variables in the production machine and put them in the .gitignore file, so you don't upload them to github.\n\n**NOTE**: We have to configure the env variables in the production machine, so docker grabs them from the local env, instead of being hardcoded, as they are in development. To configure env variables in a Linux machine we can use:\n\n`export ENV_VARIABLE_NAME=\"hello\"`\n\nAn alternative is to create a `.env` file in root typing: `vi .env` and add all the ENV variables in the file. Then, in the root folder open the `.profile` file and in the bottom, create a new instruction: `set -o allexport; source /root/.env set -o allexport`. Close and reopen the terminal and type `printenv` to see the variables.\n\nFor deployment, we can clone the repo and run `docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build`.\n\nTo update changes in production after build, we can run `docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build` again after pulling for changes in the repository. We can add the `--no-deps` if we don't want to rebuild the databases and other services that wont change in development.\n\nTo re-build the container, for whatever reason, we can pass the `--force-recreate --no-deps`. Now, this workflowis not optimal since we have to rebuild in the production server. Instead, we are going to upload the finalized image to Dockerhub and pull the built image in production.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharlie5dh%2Fdocker-node","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcharlie5dh%2Fdocker-node","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcharlie5dh%2Fdocker-node/lists"}