C:\Users\Joshua>dumpbin /nologo /dependents "C:\Program Files\nodejs\node.exe"
Dump of file C:\Program Files\nodejs\node.exe
File Type: EXECUTABLE IMAGE
Image has the following dependencies:
dbghelp.dll
WS2_32.dll
IPHLPAPI.DLL
PSAPI.DLL
USERENV.dll
ADVAPI32.dll
USER32.dll
CRYPT32.dll
bcrypt.dll
KERNEL32.dll
WINMM.dll
The promise of Docker
Consistent runtime environment
Tools for developers
Docker Desktop
Interoperable format (Docker Image)
Self documenting (Dockerfile)
Easy* to use APIs (CLI, HTTP API)
Widely adopted
Kubernetes
GitHub Actions
EKS, AKS, GKE
Humble beginnings
$ node --version # Node.js running on my host
v16.13.0
$ docker run --rm node --version # Node.js running in a container
v17.1.0
$ docker run --rm node:latest --version # <image>:<tag> the default tag is 'latest'
v17.1.0
$ docker run --rm node:16.13.0 --version # Specific Node.js running inside a container
v16.13.0
$ docker run --rm node which node # Location of Node.js binary inside the container
/usr/local/bin/node
$ docker run --rm node ldd /usr/local/bin/node # Dependencies of Node.js inside the container
linux-vdso.so.1 (0x00007ffe80b9c000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f32df703000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f32df536000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f32df3f2000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f32df3d8000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f32df3b6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f32df1f1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f32df713000
Blast off!
$ docker run \ # Run a command in a new container
--rm \ # Automatically remove the container when it exits
--interactive \ # Keep STDIN open even if not attached *
--tty \ # Allocate a pseudo-TTY *
--workdir ~ \ # Working directory inside the container
--name dev \ # Assign a name to the container
node \ # Official Node Docker Image
bash # Command# Weird no files?
root@f6513673f9f6:/home/joshua# ls -lah
total 8.0K
drwxr-xr-x 2 root root 4.0K Nov 25 18:10 .
drwxr-xr-x 1 root root 4.0K Nov 25 18:10 ..
# No vim either...
root@f6513673f9f6:/home/joshua# vim
bash: vim: command not found
Power through
root@f6513673f9f6:/home/joshua# mkdir todo
root@f6513673f9f6:/home/joshua# cd todo/# Initialize a Node.js module
root@f6513673f9f6:/home/joshua/todo# yarn init --yes# Write some code
root@f6513673f9f6:/home/joshua/todo# echo "console.log('Hello World! ');" > index.js# Write some MORE code!
root@f6513673f9f6:/home/joshua/todo# echo "console.log('Look mum no vim! ');" >> index.js# Run the program
root@f6513673f9f6:/home/joshua/todo# node .
Hello World!
Look mum no vim!
Feels like home?
# echo is great but and the very least give me vim
root@f6513673f9f6:/home/joshua/todo# apt update
root@f6513673f9f6:/home/joshua/todo# apt install vim
root@f6513673f9f6:/home/joshua/todo# vim index.js
Happy now?
root@f6513673f9f6:/home/joshua/todo## Ctrl-p, Ctrl-q => Detach from container
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
f6513673f9f6 node "docker-entrypoint.sā¦" 13 minutes ago Up 13 minutes dev
# Copying files out of the container
$ docker cp dev:/home/joshua/todo .
$ tree
.
āāā todo
āāā index.js
āāā package.json
1 directory, 2 files
# The real work begins! (launches Visual Studio Code)
$ code todo
$ docker attach dev
root@f6513673f9f6:/home/joshua/todo# ls -lah
total 20K
drwxr-xr-x 3 node node 4.0K Nov 25 19:09 .
drwxr-xr-x 3 root root 4.0K Nov 25 18:31 ..
-rw-r--r-- 1 node node 106 Nov 25 18:50 index.js # Why are you still here?
-rw-r--r-- 1 node node 96 Nov 25 19:06 package.json
drwxr-xr-x 2 node node 4.0K Nov 25 19:07 src
root@f6513673f9f6:/home/joshua/todo# rm index.js
root@f6513673f9f6:/home/joshua/todo# node .
I hate index.js files
# I don't want to maintain and sync two copies of my code# There must be a better way...
Bind mount to the rescue!
$ pwd
/home/joshua/projects/joshua-barnett/docker-for-development/todo
$ docker run --rm --interactive --tty --workdir /srv/todo --name dev \
# New mount argument
--mount type=bind,source=`pwd`,target=/srv/todo \
# Less verbose# --volume `pwd`:/srv/todo
node \
bash
root@c9d70c16b644:/srv/todo# ls
package.json src
Only difference between them is --mount will throw an error if the host directory does not exist.
Whereas --volume will stub a missing host directory for you.
The devil is in the implementation
# Outside the container + inside bind mount (Fast! )
$ wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[=> ] 157.30M 44.7MB/s eta 64s
# Inside the container + inside bind mount (Slow! )
root@c9d70c16b644:/srv/todo# wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[=> ] 150.88M 5.11MB/s eta 9m 12s
# Inside the container + outside bind mount (Fast-ish! )
root@c9d70c16b644:/tmp# wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[> ] 161.04M 40.6MB/s eta 69s
Grass is greener?
# Outside the container + inside bind mount (Fast! )
$ wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[=> ] 159.04M 45.1MB/s eta 63s
# Inside the container + inside bind mount (Fast! )
root@9e3d5e4532e2:/srv/todo# wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[=> ] 160.96M 43.0MB/s eta 67s
# Inside the container + outside bind mount (Fast! )
root@9e3d5e4532e2:/tmp# wget -q --show-progress \
https://releases.ubuntu.com/20.04.3/ubuntu-20.04.3-desktop-amd64.iso
ubuntu-20.04.3-desktop 5%[=> ] 160.21M 45.4MB/s eta 63s
It gets worse!
# Outside the container + inside bind mount (Fast! )$ time yarn install --frozen-lockfile# -- snip ---
Done in 50.21s.
4.85s user
23.67s system
96% cpu
50.398 total
# Inside the container + inside bind mount (8x Slower! )
root@6001c575a574:/srv/awesome-todo# time yarn install --frozen-lockfile
# -- snip --
Done in 402.74s.
real 6m43.053s
user 1m15.429s
sys 1m10.757s
# Size on disk
root@6001c575a574:/srv/awesome-todo# du -sh node_modules packages/*/node_modules
370M node_modules
0 packages/client/node_modules
0 packages/server/node_modules
How to handle node_modules?
Use Linux (VM etc.) for development
Not very inclusive
Install node_modules outside of the bind mount directory
# Start a MongoDB container running in the background
$ docker run \
--rm \ # Automatically remove the container when it exits
--detach \ # Run container in background and print container ID
--volume my_db_data_volume:/data/db \
# Create a named volume called 'my_db_data_volume'# Mount the named volume to the path '/data/db' in the container
--name my_db \ # Assign a name to the container
mongo # Use the latest official MongoDB image# Show running containers
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a277c6f52683 mongo "docker-entrypoint.sā¦" 3 minutes ago Up 3 minutes 27017/tcp my_db
# Execute a mongo shell (alongside the already running mongod)
$ docker exec --interactive --tty my_db mongo
> use todo
switched to db todo
> db.items.insert({"description": "Task", "done": false})
WriteResult({ "nInserted" : 1 })
> exit
$ docker rm --force my_db
my_db
# No containers running at the moment
$ docker ps --all
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# But our MongoDB data volume still exists
$ docker volume ls
DRIVER VOLUME NAME
local my_db_data_volume
# Start the container again with the same arguments
$ docker run \
--rm \
--detach \
--volume my_db_data_volume:/data/db \
--name my_db \
# This time lets also publish the MongoDB port to our host
--publish 27017:27017 \
mongo
# Query MongoDB via the shell for existing items
$ docker exec my_db \
mongo todo --quiet --eval'db.items.find().forEach(printjson)'
{
"_id" : ObjectId("61a27159b08ff2c5624a9bba"),
"description" : "Task",
"done" : false
}
# Can also use a GUI on our host
Docker networks
Every container is assigned to the default bridge network.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
f56f0ebff9c0 bridge bridge local
3aaf4000c115 host host local
861adb658ac5 none null local
# Creating a new 'client' container that will connect to 'my_db' through bridge
$ docker run --rm --name my_client --interactive --tty mongo bash
# Can't connect to 'my_db' container through 'localhost'.
root@63dcb850b14f:/# mongo todo --quiet --eval 'db.items.find().forEach(printjson)'
Error: could not connect to server 127.0.0.1:27017, connection attempt failed: SocketException:
Error connecting to 127.0.0.1:27017 :: caused by :: Connection refused :
connect@src/mongo/shell/mongo.js:372:17
@(connect):3:6
exception: connect failed
exiting with code 1
# Requires hardcoded IP address
root@63dcb850b14f:/# mongo mongodb://172.17.0.2:27017/todo --quiet --eval 'db.items.find().forEach(printjson)'
{
"_id" : ObjectId("61a2763c923c19ee6b178a5a"),
"description" : "Task",
"done" : false
}
Recommended way
Use --network <name> to create a user defined bridge network.
User-defined bridges provide automatic DNS resolution between containers.
User-defined bridges provide better isolation.
Containers can be attached and detached from user-defined networks on the fly.
Building bridges
# Create a user defined bridge network$ docker network create my_network
ec58fc0d1375c280e609adc5086fd67697f55a8a3bfbde69eaaff65d9035a1ad
$ docker network ls
docker network ls
NETWORK ID NAME DRIVER SCOPE
f56f0ebff9c0 bridge bridge local
3aaf4000c115 host host local
ec58fc0d1375 my_network bridge local
861adb658ac5 none null local
# Recreate database container with '--network'
docker run --rm --detach --volume my_db_data_volume:/data/db --name my_db --publish 27017:27017 \
--network my_network \ # New
mongo
# Recreate client container with '--network'
docker run --rm --name my_client --interactive --tty \
--network my_network \ # New
mongo \
bash
Much better
# Install dig for DNS querying
root@924e075a1b8c:/# apt update && apt install --yes dnsutils
# Query database container name 'my_db'
root@924e075a1b8c:/# dig +short my_db
172.19.0.2
# Connecting to database using hostname
root@63dcb850b14f:/# mongo mongodb://my_db:27017/todo --quiet --eval 'db.items.find().forEach(printjson)'
{
"_id" : ObjectId("61a2763c923c19ee6b178a5a"),
"description" : "Task",
"done" : false
}
Example project awesome-todo
Database
mongod - NoSQL database
Backend application
fastify - Web framework
mongoose - Data models
graphql - API Schema
mercurius - GraphQL API
Frontend application
create-react-app - Frontend boilerplate
styled-components - Minor styling tweaks
@apollo/client - GraphQL client
RTFM
Vague...
Which version of Node.js?
Which version of Yarn?
Which version of MongoDB?
Not very future proof
Will these steps still work
5+ years from now?
What if port 3001 is taken?
How do I change it?
Cross platform?
...works on my machine
Dockerizing awesome-todo
Starting our development container.
$ docker run \
--interactive \
--tty \
# Working directory
--workdir /srv/awesome-todo \
# Bind mount source into container
--volume `pwd`:/srv/awesome-todo \
# Latest Node.js LTS (at the moment)
node:16.13.0 \
bash
Linux permissions (Gotcha)
# Default user?
root@6de80090a66a:/srv/awesome-todo# whoami
root
# GID and UID of root are '0'
root@6de80090a66a:/srv/awesome-todo# cat /etc/passwd | grep root
root:x:0:0:root:/root:/bin/bash
# Creating a empty file
root@6de80090a66a:/srv/awesome-todo# touch cant-touch-this# File is owned by root inside container
root@6de80090a66a:/srv/awesome-todo# ls -lah cant-touch-this
-rw-r--r-- 1 root root 0 Nov 28 18:43 cant-touch-this
# Permissions on host
$ ls -lh
total 604K
-rw-r--r-- 1 root root 0 Nov 28 18:45 cant-touch-this
-rw-r--r-- 1 joshua joshua 468 Nov 28 18:13 package.json
drwxr-xr-x 5 joshua joshua 4.0K Nov 28 17:55 packages
-rw-r--r-- 1 joshua joshua 1.3K Nov 27 16:44 README.md
-rw-r--r-- 1 joshua joshua 589K Nov 28 18:23 yarn.lock
# GID and UID of root
$ cat /etc/passwd | grep root
root:x:0:0:root:/root:/bin/bash
# GID and UID of my user
$ cat /etc/passwd | grep joshua
joshua:x:1000:1000:,,,:/home/joshua:/usr/bin/zsh
# Which user is 1000 inside the container?
root@6de80090a66a:/srv/awesome-todo# cat /etc/passwd | grep 1000
node:x:1000:1000::/home/node:/bin/bash
Amendment --user
# Creating development shell on host
$ docker run \
--rm \
--interactive \
--tty \
--workdir /srv/awesome-todo \
--volume `pwd`:/srv/awesome-todo \
# Specifying node as the user we want to start with
--user node \
node:16.13.0 \
bash
# Creating empty file
node@b48146bd2871:/srv/awesome-todo$ touch can-touch-this
# File owned by node inside container
node@b48146bd2871:/srv/awesome-todo$ ls -lh can-touch-this
-rw-r--r-- 1 node node 0 Nov 28 18:54 can-touch-this
# File owned by me outside container
$ ls -lh can-touch-this
-rw-r--r-- 1 joshua joshua 0 Nov 28 18:54 can-touch-this
Installing node_modules
# Set the modules folder location outside of bind mount
node@b48146bd2871:/srv/awesome-todo$ echo'--modules-folder /tmp/awesome-todo/node_modules' > .yarnrc
# Try starting the server as a test
node@b48146bd2871:/srv/awesome-todo$ yarn server
yarn run v1.22.15
$ yarn workspace @awesome-todo/server start
$ fastify start -d -w -l info -P src/app.mjs
/bin/sh: 1: fastify: not found # What went wrong?# Printing ${PATH} to debug
node@b48146bd2871:/srv/awesome-todo$ yarn server
yarn run v1.22.15
$ yarn workspace @awesome-todo/server start
$ echo${PATH} | sed s/:/\\n/g | grep awesome-todo
/srv/awesome-todo/packages/server/tmp/awesome-todo/node_modules/.bin
/home/node/.config/yarn/link/tmp/awesome-todo/node_modules/.bin
/srv/awesome-todo/tmp/awesome-todo/node_modules/.bin
/srv/awesome-todo/tmp/awesome-todo/node_modules/.bin
/home/node/.config/yarn/link/tmp/awesome-todo/node_modules/.bin
/srv/awesome-todo/tmp/awesome-todo/node_modules/.bin
Done in 0.52s.
# Bug in yarn?
# Updating PATH to be correct
node@b48146bd2871:/srv/awesome-todo$ export PATH="${PATH}:/tmp/awesome-todo/node_modules/.bin"# Node.js cannot resolve the node_modules because they are not in the parent
node@b48146bd2871:/srv/awesome-todo$ yarn server
yarn run v1.22.15
$ yarn workspace @awesome-todo/server start
$ fastify start -d -w -l info -P src/app.mjs
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'config' imported from /srv/awesome-todo/packages/server/src/app.mjs
# -- snip --# Symlinking /tmp/awesome-todo/node_modules into the parent
node@b48146bd2871:/srv/awesome-todo$ ln -s /tmp/awesome-todo/node_modules
# Third times the charm
node@b48146bd2871:/srv/awesome-todo$ yarn server
yarn run v1.22.15
$ yarn workspace @awesome-todo/server start
$ fastify start -d -w -l info -P src/app.mjs
Debugger listening on ws://0.0.0.0:9320/3867d4ef-3eb9-4f38-a781-e8f6eee92592
For help, see: https://nodejs.org/en/docs/inspector
# Good stuff
Adding new dependencies
# Adding prettier for formatting server code
node@b48146bd2871:/srv/awesome-todo$ yarn workspace @awesome-todo/server add --dev prettier
# -- snip --
success Saved lockfile.
success Saved 1 new dependency.
info Direct dependencies
info All dependencies
āā prettier@2.5.0
Done in 8.43s.
Done in 8.72s.
# Adding lint script to server package
node@b48146bd2871:/srv/awesome-todo$ cat packages/server/package.json | grep lint
"lint": "prettier --check config src test",
# Running lint script
node@b48146bd2871:/srv/awesome-todo$ yarn workspace @awesome-todo/server lint
yarn workspace v1.22.15
yarn run v1.22.15
$ prettier --check config src test
Checking formatting...
All matched files use Prettier code style!
Done in 1.03s.
Done in 1.33s.
Dockerfile
We have made changes the development environment we want to share
Lets document and automate these changes by providing a Dockerfile
# ā Cannot use sha256 if you want to support multi-arch ARG NODE_VERSION="16.13.0"FROM node:${NODE_VERSION} AS base
FROM base AS install
ARG PROJECT="awesome-todo"WORKDIR /srv/${PROJECT}# If these files change...COPY package.json yarn.lock .yarnrc ./COPY packages/server/package.json packages/server/COPY packages/client/package.json packages/client/COPY packages/shared/package.json packages/shared/# ...this will run again when rebuiltRUN yarn install --frozen-lockfileFROM base AS dev
ARG PROJECT="awesome-todo"# Copy just what we want from the previous stageCOPY --from=install /tmp/${PROJECT}/node_modules /tmp/${PROJECT}/node_modules# Updating PATH variableENV PATH="${PATH}:/tmp/${PROJECT}/node_modules/.bin"
That is one chunky monkey
# Enabling buildkit
$ export DOCKER_BUILDKIT=1
$ docker build --tag awesome-todo/dev --target node_modules .
# -- snip --
Successfully built afdf832b7e20
Successfully tagged awesome-todo/dev:latest
# Eye-wateringly large image of 1.17GB!
$ docker image ls awesome-todo/dev
REPOSITORY TAG IMAGE ID CREATED SIZE
awesome-todo/dev latest 5c404aa9d660 4 minutes ago 1.17GB
# Build arguments to the rescue
$ docker build --tag awesome-todo/dev/slim --target node_modules --build-arg NODE_VERSION=16.13.0-slim .
# Bit better still quite large
$ docker image ls awesome-todo/dev/slim
REPOSITORY TAG IMAGE ID CREATED SIZE
awesome-todo/dev/slim latest 387425ea14b8 2 minutes ago 444MB
# We can go smaller
$ docker build --tag awesome-todo/dev/alpine --target node_modules --build-arg NODE_VERSION=16.13.0-alpine .
# Bit better...
$ docker image ls awesome-todo/dev/alpine
REPOSITORY TAG IMAGE ID CREATED SIZE
awesome-todo/dev/alpine latest 283748b68275 49 seconds ago 380MB
Using the pre-built image
# Creating development shell on host
$ docker run \
--rm \
--interactive \
--tty \
--workdir /srv/awesome-todo \
--volume `pwd`:/srv/awesome-todo \
--user node \
# Using our pre-built image
awesome-todo/dev/alpine \
# `bash` does not exist in alpine, using `sh` instead
yarn server
yarn run v1.22.15
$ yarn workspace @awesome-todo/server start
$ fastify start -d -w -l info -P src/app.mjs
Debugger listening on ws://0.0.0.0:9320/c3b7b8e9-a248-4c5f-896c-a8111468a369
For help, see: https://nodejs.org/en/docs/inspector
# Great, no yarn install required
What about the client?
# Creating development shell on host
$ docker run \
--rm \
--interactive \
--tty \
--workdir /srv/awesome-todo \
--volume `pwd`:/srv/awesome-todo \
--user node \
# Using our pre-built image
awesome-todo/dev/alpine \
# `bash` does not exist in alpine, using `sh` instead
yarn client
Compiled successfully!
You can now view @awesome-todo/client in the browser.
Local: http://localhost:3001
On Your Network: http://172.17.0.3:3001
Note that the development build is not optimized.
To create a production build, use npm run build.
Tired of repeating yourself?
Maintaining these commands is becoming cumbersome
Lots of arguments
Some times repeated values
--workdir /srv/awesome-todo
--volume pwd:/srv/awesome-todo
We can use Docker Compose!
We need to create a docker-compose.yaml
docker-compose.yaml
version:'3.8'services:client:# docker buildbuild:args:# --build-argNODE_VERSION:16.13.0-alpineimage:awesome-todo/dev/alpine# --tag# docker runworking_dir:/srv/awesome-todo# --workdirtty:true# --ttystdin_open:true# --interactivevolumes:# --volume-.:/srv/awesome-todo# no more `pwd`, compose resolves the cwd for uscommand:yarnclient
Adding server
version:'3.8'services:client:build:args:NODE_VERSION:16.13.0-alpineimage:awesome-todo/dev/alpineworking_dir:/srv/awesome-todotty:truestdin_open:truevolumes:-.:/srv/awesome-todocommand:yarnclientserver:# Repeating ourselves?build:args:NODE_VERSION:16.13.0-alpineimage:awesome-todo/dev/alpineworking_dir:/srv/awesome-todotty:truestdin_open:truevolumes:-.:/srv/awesome-todocommand:yarnserver# Apart from this one change?
Black magic
version:'3.8'x-monorepo:&monorepo# YAML refbuild:args:NODE_VERSION:16.13.0-alpineimage:awesome-todo/dev/alpineworking_dir:/srv/awesome-todo# Still repeating myself here...tty:truestdin_open:truevolumes:-.:/srv/awesome-todo# ...and hereservices:client:<<:*monorepo# Merging only works with objectscommand:yarnclientserver:<<:*monorepocommand:yarnserver
Variable substitution
version:'3.8'x-monorepo:&monorepo# YAML refbuild:args:NODE_VERSION:16.13.0-alpineimage:awesome-todo/dev/alpineworking_dir:${WORKDIR}# Using substitutiontty:truestdin_open:truevolumes:-.:${WORKDIR}# Using substitutionservices:client:<<:*monorepo# Merging only works with objectscommand:yarnclientserver:<<:*monorepocommand:yarnserver
Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
yarn run v1.22.15
$ yarn workspaces run lint
> @awesome-todo/client
$ prettier --check public src
Checking formatting...
All matched files use Prettier code style!
> @awesome-todo/server
$ prettier --check config src test
Checking formatting...
All matched files use Prettier code style!
> @awesome-todo/shared
$ prettier --check src
Checking formatting...
All matched files use Prettier code style!
Done in 4.57s.
Welcome to my presentation on 'Docker for development'
But first.
Let's cover why you might want to use Docker for your development environment.
Recently I went to a conference in Seattle and a I met a bunch of interesting people.
One person I met had developed a "cross platform" 2D game library, with basic examples and everything.
While at the airport I had a spare moment so I thought I'll download it, build it and run one of the examples.
So this is his documentation which I followed.
Once I had all the dependencies installed.
I ran the build script, and it compiled successfully (ignoring a few compiler warnings).
When it comes to running an example, you'll see it fails with a relatively informative error about failing to create a window.
So this is not a great experience as a developer.
Especially once who has not experience of the project.
I know a bit of C/C++ and maybe a bit of CMake, but do I know enough about GLFW to immediately know how to to fix this issue?
No!
Looking at this you might be thinking.
Well serves you right for using C.
This is why interpretted languages and JIT compilers exist.
So I can write once and run everywhere.
So given most things have some dependency or another on the operating system.
How do we solve this issue in a way where we do not have to worry about the environment we are running in.
I was going to go into more detail here about how you can access the Docker VM directly.
Explain a bit about how OverlayFS works, and other parts of how it is implemented on different platforms.
However, I don't think I've got enough time for that.
So I'll wave it away as magic for now.
But here's an article explaining a bit more about how it works.
Turns out sync file systems isn't just about moving and syncing files.
If you want things like hot reload to work you will also need to propagate and communicate file events between systems, so it gets quite complicated.
All your really need to know is bind mounts are great!
But you should pick when to use them wisely because they don't work great on all platforms and can be very slow in certain use cases.
Namely...
- Installation of node_modules
- Working directories for things like databases
```bash
$ docker inspect dev | jq '.[].GraphDriver.Data.MergedDir'
"/var/lib/docker/overlay2/100be0d438a58870743f604ae5fbe49f46a2b3d1a0cc02a7809171396fff1b10/merged"
# Enter Docker VM on macOS
$ nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock
# Inside Docker VM
$ cd /var/lib/docker/overlay2/100be0d438a58870743f604ae5fbe49f46a2b3d1a0cc02a7809171396fff1b10/merged
```
This problem with the bind mount implementation is exacerbated in I/O heavy use cases.
Such as database data directories and node_modules installation.
Now we could put this command in our readme in addition to the previous commands.
However, it would probably be a better user experience to put it in a shell script.