How to Deploy laravel with Inertia SSR

@fideloper-fly

I use this Dockerfile

# syntax = docker/dockerfile:experimental

# Default to PHP 8.2, but we attempt to match
# the PHP version from the user (wherever `flyctl launch` is run)
# Valid version values are PHP 7.4+
ARG PHP_VERSION=8.2
ARG NODE_VERSION=18
FROM fideloper/fly-laravel:${PHP_VERSION} as base

# PHP_VERSION needs to be repeated here
# See https://docs.docker.com/engine/reference/builder/#understand-how-arg-and-from-interact
ARG PHP_VERSION

LABEL fly_launch_runtime="laravel"

# copy application code, skipping files based on .dockerignore
COPY . /var/www/html

RUN composer install --optimize-autoloader --no-dev \
    && mkdir -p storage/logs \
    && php artisan optimize:clear \
    && chown -R www-data:www-data /var/www/html \
    && sed -i 's/protected \$proxies/protected \$proxies = "*"/g' app/Http/Middleware/TrustProxies.php \
    && echo "MAILTO=\"\"\n* * * * * www-data /usr/bin/php /var/www/html/artisan schedule:run" > /etc/cron.d/laravel \
    && cp .fly/entrypoint.sh /entrypoint \
    && chmod +x /entrypoint

# If we're using Octane...
RUN if grep -Fq "laravel/octane" /var/www/html/composer.json; then \
        rm -rf /etc/supervisor/conf.d/fpm.conf; \
        if grep -Fq "spiral/roadrunner" /var/www/html/composer.json; then \
            mv /etc/supervisor/octane-rr.conf /etc/supervisor/conf.d/octane-rr.conf; \
            if [ -f ./vendor/bin/rr ]; then ./vendor/bin/rr get-binary; fi; \
            rm -f .rr.yaml; \
        else \
            mv .fly/octane-swoole /etc/services.d/octane; \
            mv /etc/supervisor/octane-swoole.conf /etc/supervisor/conf.d/octane-swoole.conf; \
        fi; \
        rm /etc/nginx/sites-enabled/default; \
        ln -sf /etc/nginx/sites-available/default-octane /etc/nginx/sites-enabled/default; \
    fi

# Multi-stage build: Build static assets
# This allows us to not include Node within the final container
FROM node:${NODE_VERSION} as node_modules_go_brrr

RUN mkdir /app

RUN mkdir -p  /app
WORKDIR /app
COPY . .
COPY --from=base /var/www/html/vendor /app/vendor

# Use yarn or npm depending on what type of
# lock file we might find. Defaults to
# NPM if no lock file is found.
# Note: We run "production" for Mix and "build" for Vite
RUN if [ -f "vite.config.js" ]; then \
        ASSET_CMD="build"; \
    else \
        ASSET_CMD="production"; \
    fi; \
    if [ -f "yarn.lock" ]; then \
        yarn install --frozen-lockfile; \
        yarn $ASSET_CMD; \
    elif [ -f "pnpm-lock.yaml" ]; then \
        corepack enable && corepack prepare pnpm@latest-7 --activate; \
        pnpm install --frozen-lockfile; \
        pnpm run $ASSET_CMD; \
    elif [ -f "package-lock.json" ]; then \
        npm ci --no-audit; \
        npm run $ASSET_CMD; \
    else \
        npm install; \
        npm run $ASSET_CMD; \
    fi;

FROM base

COPY --from=node_modules_go_brrr /app/public /var/www/html/public-npm
# RUN npm install -g npm@9.6.7
# RUN npm install -g yarn@$YARN_VERSION --force

RUN rsync -ar /var/www/html/public-npm/ /var/www/html/public/ \
    && rm -rf /var/www/html/public-npm \
    && chown -R www-data:www-data /var/www/html/public

EXPOSE 8080

ENTRYPOINT ["/entrypoint"]

and Error this

2023-06-10T03:14:26.075 app[e784e5ea624483] sin [info] node: not found

2023-06-10T03:14:26.075 app[e784e5ea624483] sin [info] {"message":"node: not found\n","context":{"exception":{"class":"Inertia\\Ssr\\SsrException","message":"node: not found\n","code":0,"file":"/var/www/html/vendor/inertiajs/inertia-laravel/src/Commands/StartSsr.php:74"}},"level":400,"level_name":"ERROR","channel":"production","datetime":"2023-06-10T03:14:26.075489+00:00","extra":{}}

1 Like

Hi @bogordesain!

In order to fix the error above, you’ll have to include Node in the final image created by the Dockerfile.

Since we already use the node:${NODE_VERSION} image(w/c should contain node), the quickest change you can do is copy over the node available in that image to the final image. So right below the FROM base declaration, please add:

COPY --from=node_modules_go_brrr /usr/local/bin/node /usr/local/bin/node

Of course, please make sure to copy over its node_modules/ to the final image as well:

COPY --from=node_modules_go_brrr /app/node_modules /var/www/html/node_modules

Foot note:
The Dockerfile above contains two images, fideloper/fly-laravel and node:${NODE_VERSION}. But even so, only the final image declared will be used as the final image. Hence why we needed to copy over files from the node image to the fly-laravel image. You can read more on how multiple Dockerfile FROM declarations work here!

Also! You can run ssr as another vm to your Laravel Fly.io app by declaring it as a separate process in your fly.toml file( similar to running cron as a process ):

[processes]
  app=""
  ssr="php /var/www/html/artisan inertia:start-ssr"

Hi @bogordesain ! We’re working on some docs for this use case, thanks for the poke.

Let us know if this idea works for you (we’re testing it out soon to double check / document it for future people).

1 Like

this is good, but when i check using view source, ssr not load, just SPA only.

When i use @kathrynannetan solution this work for running machine. but on view source just show SPA.

in the docs of Inertiajs https://inertiajs.com/server-side-rendering command for Heroku like this :slight_smile:

web: php artisan inertia:start-ssr & vendor/bin/heroku-php-apache2 public/

But I don’t know with docker image,

maybe can be like this

web: php artisan octane:start & php artisan inertia:start-ssr 

?

maybe you can check this Laravel

Hi @bogordesain! I’m glad the solution worked! :fireworks:

One more step that needs to be done after setting up the ssr process, is to create communication between the “app” and “ssr” processes.

This can be done by adding a SSR_URL to the fly.toml’s [env] section, that points to the .internal address of the ssr process on port 13714:

[env]
  SSR_URL="ssr.process.<yourAppNameHerePlease>.internal:13714"

and using that env variable for the inertia.php config:

/* config/inertia.php */
'ssr' => [
    'enabled' => true,

    'url' => env('SSR_URL','http://127.0.0.1:13714')
],

Let us know if the above solution works, and if you have any additional questions as well!

1 Like

Also, thanks to your question @bogordesain, we now have this guide on Inertia SSR here!

Please check it out and let us know if anything else should be considered in the guide.

Happy flying on Fly.io!

2 Likes

Yes, It’s work. thanks bro

1 Like

Yeah… i agree for create guide. this awesome…

1 Like

Hey! Sorry to revive this thread, I’ve followed the guide and it seems to almost be working for me except for one little thing, the ssr process exits immediately. For reference, I’ve updated my Dockerfile to use Bun instead of Node and I’ve updated the start-ssr command as well.


# Install Bun under fly-laravel image
RUN cd ~ \
    && curl -fsSL https://bun.sh/install | bash \
    && cd /var/www/html

# Use yarn or npm depending on what type of
# lock file we might find. Defaults to
# NPM if no lock file is found.
# Note: We run "production" for Mix and "build" for Vite
RUN if [ -f "vite.config.js" ]; then \
    ASSET_CMD="build"; \
    else \
    ASSET_CMD="production"; \
    fi; \
    if [ -f "yarn.lock" ]; then \
    yarn install --frozen-lockfile; \
    yarn $ASSET_CMD; \
    elif [ -f "pnpm-lock.yaml" ]; then \
    corepack enable && corepack prepare pnpm@latest-8 --activate; \
    pnpm install --frozen-lockfile; \
    pnpm run $ASSET_CMD; \
    elif [ -f "bun.lockb" ]; then \
    /root/.bun/bin/bun install; \
    /root/.bun/bin/bun run $ASSET_CMD; \
    elif [ -f "package-lock.json" ]; then \
    npm ci --no-audit; \
    npm run $ASSET_CMD; \
    else \
    npm install; \
    npm run $ASSET_CMD; \
    fi;

RUN chown -R www-data:www-data /var/www/html/public
[processes]
app = ""
ssr = "php /var/www/html/artisan inertia:start-ssr --runtime=bun"

Unfortunately, here’s what I see in my machine logs for the ssr process machines:

INFO Preparing to run: `/entrypoint php /var/www/html/artisan inertia:start-ssr --runtime=bun` as root

2024-09-22T23:40:45.927 app[7811943f254958] yul [info] INFO [fly api proxy] listening at /.fly/api

2024-09-22T23:40:45.934 app[7811943f254958] yul [info] 2024/09/22 23:40:45 INFO SSH listening listen_address=[fdaa:5:a243:a7b:144:2f3b:52cb:2]:22 dns_server=[fdaa::3]:53

2024-09-22T23:40:45.974 runner[7811943f254958] yul [info] Machine started in 815ms

2024-09-22T23:40:56.941 app[7811943f254958] yul [info] INFO Main child exited normally with code: 0

2024-09-22T23:40:56.954 app[7811943f254958] yul [info] INFO Starting clean up.

2024-09-22T23:40:56.957 app[7811943f254958] yul [info] WARN could not unmount /rootfs: EINVAL: Invalid argument

2024-09-22T23:40:56.957 app[7811943f254958] yul [info] [ 11.653324] reboot: Restarting system

2024-09-22T23:40:57.287 runner[7811943f254958] yul [info] machine exited with exit code 0, not restarting 

If I fly ssh console into the machine and run php /var/www/html/artisan inertia:start-ssr --runtime=bun I see no errors but the SSR is still not working when visiting my site. Any ideas what might be the issue?