Michael_K's avatar

Run Browsershot in Laravel Sail on ARM64 Mac

We switched our hardware to ARM64 Macs and I'd like to setup my stack in Laravel Sail. On the Intel Mac, everything is running in Sail, the only problem I still have on the new device is to get Browsershot running.

The problem here is Puppeteer and the Chrome behind it which needs to run on Linux ARM technology.

There are discussions on the Browsershot GitHub page, which talk about it, but there is no solution I can adapt from. I'm not good in reading and adapting Docker files.

This one here sounds good, but Puppeteer is running over a url, which is not what I need as deployment is old school without containers https://github.com/spatie/browsershot/discussions/711

The puppeteer docs have sections about running in Docker, but I don't know how to merge that with my docker file to get it running https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker

Has anyone an example Dockerfile and docker-compose.yml where Browsershot works on an arm Mac in Laravel Sail?

0 likes
7 replies
Michael_K's avatar

The AI is on the right track, but it doesn't work. I run into errors, like

failed to solve: executor failed running [/bin/sh -c groupadd --force -g $WWWGROUP sail]: exit code: 3

I guess a problem is, the Sail docker file starts with

FROM ubuntu:22.04

and not the node image given in the example above.

Also the context: . in the docker-compose.yml doesn't work instead I get the error

failed to solve: rpc error: code = Unknown desc = failed to solve with frontend dockerfile.v0: failed to read dockerfile: open /var/lib/docker/tmp/buildkit-mount2467463578/Dockerfile: no such file or directory
Michael_K's avatar
Michael_K
OP
Best Answer
Level 1

So, we did the following configurations to make it work. It runs in docker now, it runs directly on hardware, and it runs in the CI tool. Just keep the regular puppeteer entry in the package.json to make it available where docker is not used.

Adapting the browsershot config to add chromium host configuration.

    'chromium' => [
        'host' => env('BROWSERSHOT_CHROMIUM_HOST', '127.0.0.1'),
    ],

Change docker compose to add chromium and tmp folder shared between chromium and laravel.test

# For more information: https://laravel.com/docs/sail
version: "3"
services:
    laravel.test:
        build:
            context: ./docker/8.1
            dockerfile: Dockerfile
            args:
                WWWGROUP: "${WWWGROUP}"
        image: sail-8.1/app
        extra_hosts:
            - "host.docker.internal:host-gateway"
        ports:
            - "${APP_PORT:-80}:80"
            - "${VITE_PORT:-5173}:${VITE_PORT:-5173}"
        environment:
            WWWUSER: "${WWWUSER}"
            LARAVEL_SAIL: 1
        volumes:
            - "tmpfiles:/tmp"
            - ".:/var/www/html"
        networks:
            - sail
        depends_on:
            - mysql
            - redis
            - meilisearch
            - minio
            - mailhog
            - chromium

...

    chromium:
        image: zenika/alpine-chrome
        command:
            [
                chromium-browser,
                "--headless",
                "--disable-gpu",
                "--remote-debugging-address=0.0.0.0",
                "--remote-debugging-port=9222",
            ]
        cap_add:
            - SYS_ADMIN
        volumes:
            - tmpfiles:/tmp
        networks:
            - sail
networks:
    sail:
        driver: bridge
volumes:
    tmpfiles:
        driver: local
    sail-mysql:
        driver: local
    sail-redis:
        driver: local

In the docker file add the dependencies and install puppeteer. Add env to skip puppeteer download

ENV PUPPETEER_SKIP_DOWNLOAD=true

...

    && apt-get install -y gconf-service libgconf-2-4 libgdk-pixbuf2.0-0 libappindicator1 libgbm-dev libxshmfence-dev \
    && npm install --location=global --unsafe-perm puppeteer \

Where using Browsershot, add the setRemoteInstance function.

            echo Browsershot::html($content)
                ->setNodeBinary(config('browsershot.node'))
                ->setNpmBinary(config('browsershot.npm'))
                ->setRemoteInstance(config('browsershot.chromium.host'))

To learn the IP address of the chromium container and to subsequently set BROWSERSHOT_CHROMIUM_HOST in your .env, you run the command (with sail up running)

docker compose ps

Then take NAME of the chromium container, for example project-name-chromium-1 and run the command

docker inspect project-name-chromium-1

Under NetworkSettings->Networks->IPAddress is the IP you need to add to your config.

This should be all to get it working.

5 likes
Michael_K's avatar

By the way, the config can be made easier with

'chromium' => [
    'host_ip' => gethostbyname(env('BROWSER_SHOT_CHROMIUM_HOSTNAME')),
],

and in your .env

BROWSER_SHOT_CHROMIUM_HOSTNAME="chromium"

and consumed in your browsershot call with

->setRemoteInstance(config('browsershot.chromium.host_ip'))
thursday_dan's avatar

Great post, going to try this myself. Does browsershot have a config file to publish?

1 like
ArtemPD's avatar

Hello everyone, I am very grateful @michael_k for this post as it has helped me greatly. I also want to share my solution, which may be useful to someone.

1)First of all, we need update docker-compose.yml (we will add chromium service-container) :

chromium:
  image: zenika/alpine-chrome
  command:
    [
      chromium-browser,
      "--headless",
      "--disable-gpu",
      "--remote-debugging-address=0.0.0.0",
      "--remote-debugging-port=9222",
    ]
  cap_add:
    - SYS_ADMIN
  volumes:
    - tmpfiles:/tmp
  ports:
    - "9222:9222"
  networks:
    sail:
      ipv4_address: 172.22.0.100

2)The chromium service-container use a static IP, so we need to update our sail network:

networks:
  sail:
    ipam:
      driver: default
      config:
        - subnet: "172.22.0.1/24"

3)In the next step, you should update volumes:

volumes:
    tmpfiles:
        driver: local

4)Don't forget add this volume to your service-container with laravel:

services:
    laravel.test:  # Here will be name of your service-container
		...
		 volumes:
            - 'tmpfiles:/tmp' # This line
            - '.:/var/www/html'
		...

		 depends_on:
		 - chromium # This line too

Item 5: Rebuild docker

# Step 1: Stop the containers
./vendor/bin/sail down

# Step 2: Rebuild the containers without cache
./vendor/bin/sail build --no-cache

# Step 3: Start the containers
./vendor/bin/sail up

Thats all, now you can use browsershot like this

$bs = Browsershot::html($html)
            ->format('A4')
            ->showBackground()
            ->margins(10, 10, 10, 10);
if (config('browser-shot.HOST')) {
    $bs->setRemoteInstance(config('browser-shot.HOST'), '9222')
        ->waitUntilNetworkIdle();
}

return $bs->pdf();

for convenience, i have created browser-shot.php in config directory

<?php

return [
    'PATH_CHROME' => env('PATH_CHROME'),
    'PATH_NODE_MODULES' => env('PATH_NODE_MODULES'),
    'HOST' => env('BROWSERSHOT_CHROMIUM_HOST', '127.0.0.1'),
    'NODE' => env('NODE', '/usr/bin/node'),
    'NPM' => env('NPM', '/usr/bin/npm'),
];

Don't forget add this line to your env file

BROWSERSHOT_CHROMIUM_HOST=172.22.0.100

Have a good day!

1 like

Please or to participate in this conversation.