Search code examples
dockerdocker-composesveltevitesveltekit

Dockerized Sveltkit app: Hot reload not working


With the help from SO community I was finally able to dockerize my Sveltekit app and access it from the browser (this was an issue initially). So far so good, but now every time I perform a code change I need to re-build and redeploy my container which obviously is not acceptable. Hot reload is not working, I've been trying multiple things I've found online but none of them have worked so far.

Here's my Dockerfile:

FROM node:19-alpine

# Set the Node environment to development to ensure all packages are installed
ENV NODE_ENV development

# Change our current working directory
WORKDIR /app

# Copy over `package.json` and lock files to optimize the build process
COPY  package.json package-lock.json ./
# Install Node modules
RUN npm install

# Copy over rest of the project files
COPY . .

# Perhaps we need to build it for production, but apparently is not needed to run dev script.
# RUN npm run build

# Expose port 3000 for the SvelteKit app and 24678 for Vite's HMR
EXPOSE 3333
EXPOSE 8080
EXPOSE 24678

CMD ["npm", "run", "dev"]

My docker-compose:

version: "3.9"

services:
  dmc-web:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: dmc-web
    restart: always
    ports:
      - "3000:3000"
      - "3010:3010"
      - "8080:8080"
      - "5050:5050"
      - "24678:24678"
    volumes:
      - ./:/var/www/html

the scripts from my package.json:

    "scripts": {
        "dev": "vite dev --host 0.0.0.0",
        "build": "vite build",
        "preview": "vite preview",
        "test": "playwright test",
        "lint": "prettier --check . && eslint .",
        "format": "prettier --write ."
    },

and my vite.config.js:

import { sveltekit } from '@sveltejs/kit/vite';
import {defineConfig} from "vite";

export default defineConfig({
    plugins: [sveltekit()],
    server: {
        watch: {
            usePolling: true,
        },
        host: true, // needed for the DC port mapping to work
        strictPort: true,
        port: 8080,
    }
});

any idea what am I missing? I can reach my app at http://localhost:8080 but cannot get to reload the app when a code change happens.

Thanks.


Solution

  • Solution

    The workspace in question does not work simply because it does not bind-mount the source directory. Other than that, it has no problem whatsoever.

    Here's working code at my github: https://github.com/rabelais88/stackoverflow-answers/tree/main/74680419-svelte-docker-HMR

    1. Proper bind mount in docker-compose

    The docker-compose.yaml from the question only mounts the result of previous build, not the current source files.

    # 🚨wrong
        volumes:
          - ./:/var/www/html
    # ✅answer
        volumes:
          # it avoids mounting the workspace root
          # because it may cause OS specific node_modules folder
          # or build folder(.svelte-kit) to be mounted.
          # they conflict with the temporary results from docker space.
          # this is why many mono repos utilize ./src folder
          - ./src:/home/node/app/src
          - ./static:/home/node/app/static
          - ./vite.config.js:/home/node/app/vite.config.js
          - ./tsconfig.json:/home/node/app/tsconfig.json
          - ./svelte.config.js:/home/node/app/svelte.config.js
    

    2. dockerfile should not include file copy and command

    dockerfile does not always have to include command. it is necessary when 1)the result has to be preserved 2)the process lifecycle is critical to image. In this case 1)the result is not quite certain because the source may not be complete at the moment of booting, 2) the process lifecycle is not really important because the user may manually execute or close the container. The local development environment for VSCode + Docker, a.k.a VSCode devcontainer, also uses sleep infinity command for this reason.

    As mentioned above, the code cannot be copied to docker space because it would conflict with bind-mounted files. To avoid both files collide, just remove COPY and CMD command from dockerfile and add more commands at docker-compose.yaml

    # dockerfile
    
    # 🚨wrong
    COPY  package.json package-lock.json ./
    RUN npm install
    COPY . .
    # ...
    CMD ["npm", "run", "dev"]
    
    # ✅answer
    COPY  package*.json ./
    RUN npm install
    # comment out COPY and CMD
    # COPY . .
    # ...
    # CMD ["npm", "run", "dev"]
    

    and add command to docker-compose

    # docker-compose.yaml
    services:
      svelte:
        # ...
        command: npm dev
    

    and rest of configs in the question are not necessary. you can check this out from my working demo at Github

    Edit

    I just did this, but when running it I'm getting Error: Cannot find module '/app/npm dev'.

    the answer uses arbitrary settings. the volumes and CMD may has to be changed accordingly.

    i.e.)

    # docker-compose.yaml
    volumes:
      - ./src:/$YOUR_APP_DIR/src
      - ./static:/$YOUR_APP_DIR/static
      # ...
    

    I've used /home/node/app as WORKDIR because /home/node is used as main WORKDIR for official node docker image. However, it is not necessary to use the same folder. If you're going to use /home/node/app, make sure create the folder before use.

    RUN mkdir -p /home/node/app
    WORKDIR /home/node/app