How to do remote development for a Laravel app using Vite

I’m successfully using Okteto for my legacy vanilla PHP app. We’re working on reimplementing our app from scratch in Laravel, and I’m trying to figure out the best way to develop it in Okteto. The issue I’m running into is how to set up and run the web server/frontend.

In my production web server container, I’d like to avoid having npm installed, so my Dockerfile is running npm build in order to compile the static files, and then copying them into the container:

FROM node:21 as frontend

ARG APP_PATH
ARG DEV_ENVIRONMENT

COPY --from=composer_base "$APP_PATH" "$APP_PATH"

WORKDIR "$APP_PATH"

RUN npm install && \
    npm run build


###############################


FROM nginx:1.25-alpine as web_server

ARG APP_PATH
ARG DEV_ENVIRONMENT

ENV APP_PATH="$APP_PATH"

WORKDIR "$APP_PATH"

COPY docker/web_server/nginx.conf.template /etc/nginx/templates/default.conf.template

COPY --from=frontend "${APP_PATH}/public" "${APP_PATH}/public"

This way, in production, the intention is to just run the web_server image, which will serve the statically compiled assets.

For local development, my docker-compose.yml file runs both the web_server image and also the frontend image with an overridden command, to run npm run dev and run the vite dev server for hot module reloading:

# ...
  xiam.web:
    build:
      context: .
      target: web_server
      args:
        DEV_ENVIRONMENT: true
    image: xiam/web_server:develop
    container_name: xiam-web
    ports:
      - '${APP_PORT:-80}:80'
    environment:
      FPM_HOST: "xiam-fpm:9000"
    volumes:
      - './public:/app/public'
    networks:
      - xiam
    depends_on:
      - xiam.fpm
      - xiam.frontend


  xiam.frontend:
    build:
      context: .
      target: frontend
      args:
        DEV_ENVIRONMENT: true
    image: xiam/frontend:develop
    container_name: xiam-frontend
    command: [ "npm", "run", "dev" ]
    ports:
      - '${VITE_PORT:-5173}:${VITE_PORT:-5173}'
    volumes:
      - '.:/app'
      - '/app/node_modules/'
    networks:
      - xiam

# ...

Both of these are working. However, now I’m trying to build my Okteto manifest to get remote development working in my self-hosted Okteto cluster, and I’m not sure how to achieve the same thing as I’m doing locally in docker compose.

I was hoping for some insight from you guys, since Laravel and Vite are also pretty new for me, so I’m not clear on how this should be configured. It looks like Vite detects that there is a dev server running by looking for a file named hot in the public directory on the web server container. My first thought was to add a second container to the web_server Deployment to run the frontend image with the npm run dev command. If I could sync both containers back to my local machine, the hot file should be synced into the web_server container. But based on an earlier discussion thread I found, it looks like Okteto doesn’t support running okteto up to multiple containers in the single pod, so that doesn’t seem like a workable solution.

I have some other ideas about other possible approaches, but I was curious if someone with more Laravel/Vite experience might be able to offer some insight. Thanks!

I thought I’d come back and share what I was able to get working. I moved the frontend container (for running npm run dev) to its own, separate deployment in my Helm chart, along with the existing fpm-server (for php-fpm) and web-server (for nginx) deployments.

I then set up each of the three deployments as separate sections in the dev section of my Okteto manifest:

dev:
  xiam-fpm-server:
    command: [ "php-fpm" ]
    reverse:
      - 9003:9003
    sync:
      - ".:/app"
    securityContext:
      runAsUser: 82
      runAsGroup: 82
      fsGroup: 82

  xiam-frontend:
    command: [ "npm", "run", "dev" ]
    forward:
      - 5173:5173
    sync:
      - ".:/app"

  xiam-web-server:
    command: [ "nginx", "-g", "daemon off;" ]
    sync:
      - "public:/app/public"
    securityContext:
      runAsUser: 101
      runAsGroup: 101
      fsGroup: 101

This got me most of the way there. However, I was getting a mixed-content error, since Okteto serves my site via HTTPS, but the Vite websocket was using plain HTTP via localhost.

This was tricky. I tried setting up a second service and ingress in order to serve the Vite websocket via Okteto’s NGINX proxy, but I wasn’t able to get that working. I was adding the appropriate annotations to my Ingress in order to configure Nginx to support websockets, but it still wasn’t working. I suspect it’s because recent versions of Okteto actually have two nested Nginx proxies, and I probably wasn’t being able to configure both. This wouldn’t have been a great solution anyway, because as far as I’m aware, there isn’t any way for Vite to receive its configuration via environment variables, so I would have had to hardcode my Okteto URL in the Vite config file, which would mean it wouldn’t be usable by multiple developers or even multiple namespaces.

Vite can be configured to serve the websocket using HTTPS directly, but then it has to be configured with the TLS certificate and key files, and, again, hardcoding the configuration.

Fortunately, after hours of research and experimentation, I managed to find a solution that seems to meet my needs. There is a free service called https://backloop.dev/ which is designed specifically for this purpose. Any subdomains of *.backloop.dev resolve to 127.0.0.1, and they maintain letsencrypt certificates for the wildcard domain so they will be trusted by your browser. They provide an accompanying NPM package to automatically configure the certificates in Vite. So, here is the relevant section of my vite.config.js file:

import backloopHttpsOptions      from 'backloop.dev';

export default defineConfig(
	{
		server: {
			https: backloopHttpsOptions,

			hmr: {
				host: 'xiam.backloop.dev'
			},
		},
	});

With this configuration, I’m able to open up three terminals, run okteto up xiam-fpm-server, okteto up xiam-web-server, and okteto up xiam-frontend, and get a working development environment with syncing and hot reloading.

In the future I’d like to get the web-server and frontend components incorporated as services under the primary fpm-server service, but this wasn’t working for me yet. When I tried to run okteto up in that configuration, the dev container failed to come up, with the error, can't create '/var/okteto/remote/authorized_keys': File exists. I suspect this is due to the fact that each of my three containers runs as a different user, so there may be a permissions issue with the persistentVolume shared by all three development pods. For now, I’m happy to have it working, but I’ll keep pecking away at the combined approach.

1 Like

Thanks for contributing your solution @dmccorma! I will try to play with it this weekend and see if I can find some extra optimizations (caveat: I’m a Vite n00b).

That’d be great, I’m interested to hear what you find. I am new to Vite as well, which has made this process much more difficult – I’m fumbling around trying to make one unfamiliar piece of software work in another unfamiliar piece of software. At least you’re well versed in Okteto, so at least there’s only one unknown for you :slight_smile:

For my part, I’m working on reworking all of my containers to run under a common userid, to hopefully sidestep any issues Okteto has with services running as different userid’s. By default, Nginx runs under the nginx user, which is userid 101 in the nginx:1.25-alpine image I’m using as a base, whereas php-fpm runs under www-data, which is 82 in the php:8.3-fpm-alpine image I’m using. I’m experimenting with creating a new user in each one, with a known userid, so that they will all work predictably, even when sharing a common persistent volume.