Dockerizing your Remix app is straightforward and makes your app independent of hosting providers! Let's get to it and build a production ready Dockerfile! π€πΏ
Basic Dockerfile
To get started, let's create a basic Dockerfile for our Remix app:
FROM node:21-bullseye-slim
WORKDIR /myapp
ADD . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
Make sure to change the command to match your start script if it differs from npm start. You can then build it like this:
docker build -t remix-minimal .
Improvements
As always with Docker, you can improve your Docker image in a few easy ways. Let's look at the two most obvious ways: copying only what you need (improving caching!) and multi-stage builds (improving the image size!) π
Copying Only What You Need
A lot of the time your docker build takes comes from expensive I/O operations or the inability to cache single steps of your build process.
.dockerignore π€·
Firstly, create a .dockerignore file with everything that you do not want in your Docker image. This usually includes stuff like your node_modules, compiled files, or documentation. Simply write the folder names that you want to be ignored in a file, or copy an existing one.
node_modules
README.md
LICENSE
.env
Separate Copy Steps
If you are actively developing your app and you need to constantly rebuild your Docker Image, chances are that your code changes a lot more than your dependencies. You can profit from that fact by separating the install and build steps. Docker will then only re-install your dependencies if they actually changed! π₯³
FROM node:21-bullseye-slim
WORKDIR /myapp
### Copy the lock and package file
ADD package.json .npmrc ./
### Install dependencies
RUN npm install --include=dev
### Copy your source code
### If only files in the src folder changed, this is the only step that gets executed!
ADD . .
RUN npm run build && npm prune --omit=dev
CMD ["npm", "start"]
Multi-Stage Builds
If you want to be a bit fancier or you need a smaller Docker image, you can also use multi-stage builds. This reduces the final image size by only including necessary runtime dependencies.
We'll take the Dockerfile from the previous step as the template but extend it to use multi-stage builds. We'll build the app in one stage and use a smaller base image for the final stage.
# base node image
FROM node:21-bullseye-slim as base
# set for base and all layer that inherit from it
ENV NODE_ENV production
# Install all node_modules, including dev dependencies
FROM base as deps
WORKDIR /myapp
ADD package.json .npmrc ./
RUN npm install --include=dev
# Setup production node_modules
FROM base as production-deps
WORKDIR /myapp
COPY --from=deps /myapp/node_modules /myapp/node_modules
ADD package.json .npmrc ./
RUN npm prune --omit=dev
# Build the app
FROM base as build
WORKDIR /myapp
COPY --from=deps /myapp/node_modules /myapp/node_modules
ADD . .
RUN npm run build
# Finally, build the production image with minimal footprint
FROM base
WORKDIR /myapp
COPY --from=production-deps /myapp/node_modules /myapp/node_modules
COPY --from=build /myapp/build /myapp/build
COPY --from=build /myapp/public /myapp/public
ADD . .
CMD ["npm", "start"]
Credits
The Dockerfile is based on one of the official Remix stacks!
Next Steps
How was your experience developing and deploying your Remix app? I've really enjoyed it so far, although there is still a lot of work to do to make it truly great. I'd love to hear your opinions!
If you want to go further, check out Sliplane to deploy all your dockerized apps!
Top comments (4)
Curious about image size and build time after each improvement step? π
Also, please let me know if there are any other frameworks you'd like to have a tutorial for!
Jupyter Notebooks? π
aye π«‘