Vite on Laravel with Caddy

Has anyone had success using Caddy with Vite? This configuration works absolutely fine with Traefik but not Caddy and I can’t figure out why.

Dockerfile.node:

FROM node:20 AS base

############################################
# Development Image
############################################
FROM base AS development

# We can pass USER_ID and GROUP_ID as build arguments
# to ensure the node user has the same UID and GID
# as the user running Docker.
ARG USER_ID
ARG GROUP_ID

# Switch to root so we can set the user ID and group ID
USER root

# Script to handle existing group ID
RUN if getent group "$GROUP_ID" > /dev/null; then \
        moved_group_id="99$GROUP_ID"; \
        existing_group_name=$(getent group "$GROUP_ID" | cut -d: -f1); \
        echo "Moving GID of $existing_group_name to $moved_group_id..."; \
        groupmod -g "$moved_group_id" "$existing_group_name"; \
    fi

# Script to handle existing user ID
RUN if getent passwd "$USER_ID" > /dev/null; then \
        moved_user_id="99$USER_ID"; \
        existing_username=$(getent passwd "$USER_ID" | cut -d: -f1); \
        echo "Moving UID of $existing_username to $moved_user_id..."; \
        usermod -u "$moved_user_id" "$existing_username"; \
    fi

# Set the user ID and group ID for the node user
RUN groupmod -g $GROUP_ID node && usermod -u $USER_ID -g $GROUP_ID node

# Drop privileges back to node user
USER node

############################################
# CI Image
############################################
FROM base AS ci

docker-compose.dev.yml

services:
  caddy:
    build:
      target: development
      dockerfile: Dockerfile.caddy
    ports:
      - "80:80"
      - "443:443"
    networks:
      - development
    volumes:
      - ./.infrastructure/conf/caddy/dev/Caddyfile:/etc/caddy/Caddyfile
      - ./.infrastructure/conf/caddy/dev/certificates:/etc/caddy/certificates
    deploy:
      placement:
        constraints:
          - node.role==manager
      update_config:
        parallelism: 1
        delay: 5s
        order: stop-first
  php:
    build:
      target: development
      args:
        USER_ID: ${SPIN_USER_ID}
        GROUP_ID: ${SPIN_GROUP_ID}
      dockerfile: Dockerfile.php
    stop_signal: SIGTERM
    volumes:
      - .:/var/www/html/
    networks:
      - development
    depends_on:
      mariadb:
        condition: service_healthy
  node:
    build:
      target: development
      args:
        USER_ID: ${SPIN_USER_ID}
        GROUP_ID: ${SPIN_GROUP_ID}
      dockerfile: Dockerfile.node
    volumes:
      - .:/usr/src/app/
    working_dir: /usr/src/app/
    networks:
      - development
  mailpit:
    image: axllent/mailpit
    networks:
      - development
  schedule:
    build:
      target: development
      args:
        USER_ID: ${SPIN_USER_ID}
        GROUP_ID: ${SPIN_GROUP_ID}
      dockerfile: Dockerfile.php
    volumes:
      - .:/var/www/html/
    networks:
      - development
    depends_on:
      php:
        condition: service_started
  queue:
    build:
      target: development
      args:
        USER_ID: ${SPIN_USER_ID}
        GROUP_ID: ${SPIN_GROUP_ID}
      dockerfile: Dockerfile.php
    volumes:
      - .:/var/www/html/
    networks:
      - development
    depends_on:
      php:
        condition: service_healthy
  mariadb:
    image: mariadb:11.4
    networks:
      - development
    volumes:
      - ./.infrastructure/volume_data/mariadb/database_data/:/var/lib/mysql
    environment:
      MARIADB_ROOT_PASSWORD: "rootpassword"
      MARIADB_DATABASE: "laravel"
      MARIADB_USER: "mysqluser"
      MARIADB_PASSWORD: "mysqlpassword"
    ports:
      - target: 3306
        published: 3306
        mode: host
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      start_period: 10s
      interval: 10s
      timeout: 5s
      retries: 3
networks:
  development:

vite.config.js

import fs from 'fs';
import laravel from 'laravel-vite-plugin';
import { defineConfig } from 'vite';

export default defineConfig({
    server: {
        host: '0.0.0.0',
        hmr: {
            host: 'vite.dev.test',
            clientPort: 443,
        },
        https: {
            key: fs.readFileSync(
                '/usr/src/app/.infrastructure/conf/caddy/dev/certificates/local-dev-key.pem',
            ),
            cert: fs.readFileSync(
                '/usr/src/app/.infrastructure/conf/caddy/dev/certificates/local-dev.pem',
            ),
        },
    },
    plugins: [
        laravel({
            input: 'resources/js/app.jsx',
            ssr: 'resources/js/ssr.jsx',
            refresh: true,
        }),
    ],
});

Caddyfile

https://vite.dev.test {
	reverse_proxy node:5173
	tls /etc/caddy/certificates/local-dev.pem /etc/caddy/certificates/local-dev-key.pem
}

https://laravel.dev.test {
	reverse_proxy php:8080

	tls /etc/caddy/certificates/local-dev.pem /etc/caddy/certificates/local-dev-key.pem
}

This works fine with Traefik but as soon as I try to use Caddy it fails.

If I visit https://vite.dev.test/ in my browser I get a 502 error.

This is the browser console for https:/laravel.dev.test:

GET
https://vite.dev.test/resources/js/app.jsx
NS_ERROR_CORRUPTED_CONTENT

GET
https://vite.dev.test/@vite/client
NS_ERROR_CORRUPTED_CONTENT

GET
https://vite.dev.test/resources/js/Pages/Auth/Login.jsx
CORS Failed

GET
https://vite.dev.test/@react-refresh
NS_ERROR_CORRUPTED_CONTENT

Loading module from “https://vite.dev.test/@react-refresh” was blocked because of a disallowed MIME type (“”).
[login](https://laravel.dev.test/login)
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://vite.dev.test/@react-refresh. (Reason: CORS request did not succeed). Status code: (null).

Loading module from “https://vite.dev.test/resources/js/Pages/Auth/Login.jsx” was blocked because of a disallowed MIME type (“”).
[login](https://laravel.dev.test/login)
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://vite.dev.test/resources/js/Pages/Auth/Login.jsx. (Reason: CORS request did not succeed). Status code: (null).

Loading module from “https://vite.dev.test/@vite/client” was blocked because of a disallowed MIME type (“”).
[login](https://laravel.dev.test/login)
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://vite.dev.test/@vite/client. (Reason: CORS request did not succeed). Status code: (null).

Loading module from “https://vite.dev.test/resources/js/app.jsx” was blocked because of a disallowed MIME type (“”).
[login](https://laravel.dev.test/login)
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://vite.dev.test/resources/js/app.jsx. (Reason: CORS request did not succeed). Status code: (null).

Does anyone have any ideas?