add nginx, multistage dockerfile, testing, hot reloading#1
add nginx, multistage dockerfile, testing, hot reloading#1jeffzyliu wants to merge 2 commits intotmonfre:mainfrom
Conversation
| RUN npm ci | ||
| COPY src src | ||
| COPY webpack* . | ||
| COPY .eslintrc.json . | ||
| COPY .babelrc . | ||
| COPY tsconfig.json . |
There was a problem hiding this comment.
I chose to not copy all; this pretty much just excludes default.conf from the dependencies of the builder image. This way, you can change the nginx stuff without forcing webpack to rebuild the same source code. Super fast.
| FROM dev AS test | ||
| COPY . . | ||
| CMD ["npm", "run", "test"] |
There was a problem hiding this comment.
This may look like major overkill here, but the purpose is for ci/cd environments where you can spin up a separate lint and unit/integration test image without making the dev or prod images depend on it.
There was a problem hiding this comment.
Actually better practice might be to use RUN instead of CMD so it blocks building of the image if checks dont pass. ¯_(ツ)_/¯
| # this still installs all the non-dev dependencies; removing react etc would be better | ||
| RUN npm ci --production |
There was a problem hiding this comment.
If you were to run this as an express server, the cleanest way would be to put everything in devDependencies so this only installs express. That's kind of bad coding practices though so it's a pick your poison thing. These images are already way smaller when the devDependencies are pruned so it's not like we have a lack of space right now.
| FROM nginx:mainline-alpine AS nginx | ||
| COPY --from=builder /app/build /usr/share/nginx/html |
There was a problem hiding this comment.
This is the magic of multistage dockerfiles: we use an entirely separate image(nginx) to host the web server. This image literally doesnt even have node, and it has none of the things in this repo actually except for default.conf (and the webpack output). We use the --from=builder option to let Docker know that this image depends on the builder image, so it builds the builder before this one.
There was a problem hiding this comment.
Multistage dockerfiles also allow for parallelism; it finds layers that have no dependencies and runs them simultaneously, blocking when there's a dependency such as copy --from
| # redirects any sub-routes to root for anything containing extension except html | ||
| rewrite ([^\/]*)\.(?!html)(.*)$ /$1.$2; |
There was a problem hiding this comment.
This is really hobbled together; it does the same thing as your app.use in ./app.js where it translates all requests for whatever/whatever.ext to just ./whatever.ext. Except for html, which is handled in the location / thing.
| - ".:/app" | ||
| depends_on: | ||
| - server | ||
| - "./src:/app/src" | ||
| - "./app.js:/app/app.js" |
There was a problem hiding this comment.
This I already mentioned. These are the only source files you'll be editing that need hot reloading so it should suffice.
| build: | ||
| context: . | ||
| target: nginx | ||
| # target: prod | ||
| restart: unless-stopped | ||
| ports: | ||
| - "9090:9090" | ||
| depends_on: | ||
| - db | ||
| # - "8080:8080" | ||
| - "80:80" |
There was a problem hiding this comment.
Swap between prod and nginx to test both approaches
| @@ -0,0 +1,6 @@ | |||
| version: '3.8' | |||
There was a problem hiding this comment.
Chose to keep this as a docker compose even though it's technically redundant bc it makes the rebuild interface a bit cleaner
| "clean": "rimraf build", | ||
| "docker:debug": "docker-compose -f docker-compose.debug.yml -p example_webapp_debug up", | ||
| "docker:prod": "docker-compose -f docker-compose.prod.yml -p example_webapp_prod up" | ||
| "dev": "npm run build:watch & npm run build:run", |
There was a problem hiding this comment.
This is the hacky solution: doing build:watch asyncronously and build:run in the foreground. So webpack watch AND nodemon are both running, each hot reloading the part it's responsible for. Never need to install node modules on the host machine at all!
Well you need to do that for vscode, but I intend to figure out how to develop in the container too so you really don't touch the host.
| "docker:clean": "docker image prune", | ||
| "docker:debug": "docker-compose -f docker-compose.debug.yml -p example_webapp_debug up --build", | ||
| "docker:prod": "docker-compose -f docker-compose.prod.yml -p example_webapp_prod up --build", | ||
| "docker:test": "docker-compose -f docker-compose.test.yml -p example_webapp_test up --build" |
There was a problem hiding this comment.
On the three compose instructions: --build rebuilds the image (using the cache ofc) which allows you to develop faster bc it responds to any source code/config/dependency changes without you having to manually rebuild the images.
This leaves a bunch of hollow image aliases though bc it doesnt delete the old ones when you rebuild. Docker image prune will delete the deprecated images to clean up space.
sending a draft PR just to give you a pretty diff to look at/comment on