У розробці npm run dev запускає dev-сервер Vite (порт 5173) і створює файл public/hot з його адресою. Директива @vite бачить цей файл і підключає скрипти не зі зібраних файлів, а з dev-сервера, - звідси гаряче перезавантаження (HMR).
Чому в Docker це часто не працює з коробки:
- Vite за замовчуванням слухає лише
localhostусередині контейнера - з браузера на хості до нього не достукатися; - порт 5173 не опубліковано;
- у
public/hotзаписується адреса, яку бачить контейнер, а браузер має звертатися до хоста.
Налаштування для контейнера:
// vite.config.js
export default defineConfig({
plugins: [laravel({ input: ['resources/css/app.css', 'resources/js/app.js'], refresh: true })],
server: {
host: '0.0.0.0', // слухати всі інтерфейси контейнера
port: 5173,
strictPort: true,
hmr: { host: 'localhost' }, // адреса, яку бачить браузер
},
});
# compose.yaml
services:
app:
ports:
- "127.0.0.1:5173:5173"
Laravel Sail робить саме це: публікує VITE_PORT і запускає sail npm run dev всередині контейнера.
Типові симптоми:
- сторінка без стилів і помилки
ERR_CONNECTION_REFUSEDдо:5173- порт не опубліковано чи Vite слухає лишеlocalhost; - стилі є, але зміни не підхоплюються - файлова система через bind mount не надсилає подій про зміни (буває на Windows і macOS), допомагає
server.watch.usePolling: true; - у продакшені запити до
:5173- у образ потрапив залишений файлpublic/hot. Його треба додати в.dockerignore.
Продакшен - зібрані файли, без Node в образі:
FROM node:24-alpine AS assets
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM php:8.5-fpm-alpine
COPY --from=assets /app/public/build /var/www/html/public/build
npm run build створює public/build з маніфестом і хешованими іменами файлів, а @vite читає маніфест. Окрема стадія збирання означає, що Node, node_modules і вихідні JS-файли не потрапляють у фінальний образ.
Помилка «Unable to locate file in Vite manifest» у контейнері - файли фронтенду не зібрано або не скопійовано в образ (чи public/build перекрито томом).