DEV Community

Cover image for The Complete Guide to Containerize Any Chromium Browsers Using Distrobox on Any Linux Distros
Archer Allstars
Archer Allstars

Posted on

The Complete Guide to Containerize Any Chromium Browsers Using Distrobox on Any Linux Distros

Ever wonder how to install your web browser in a container with everything working, e.g. hardware accelerations, usable PWAs shortcuts, keyring encryption, light/dark theme scheme that follows the host, auto-update, etc.? Basically, there's zero downside plus these many benefits:

  • This won't pull a ton of dependencies directly on your base system. It's always very hard to keep track of those residues, you know it! Your base system (host) would also be more stable, less prone to upgrade conflicts in the process.

  • A clear separation of config files. You can always start anew easily. You can delete the container along with everything inside it without affecting your system in any way.

  • Better performance if you're running on a distro that doesn't deliver x86-64-v3 microarchitecture optimization packages (99% of all distros out there).

  • This setup works on all distros, without exceptions!

  • Probably many more, depending on your use cases.


Table of contents

  1. Installing Distrobox and Podman
  2. Configure Distrobox to use Podman
  3. Create a container
  4. Enable x86-64-v3 packages
  5. Install required packages
  6. Install GPU driver
  7. Prepare directories for PWAs
  8. Symlink with the user directories on the host
  9. Install your browser
  10. Export the browser to the host
  11. Create a script to run the browser with hardware acceleration flags
  12. Modify the browser desktop file that we previously exported on the host
  13. Fixing the PWAs issue in the container
  14. Auto-update the container to which will also update the browser and everything inside with zero maintenance
  15. Check the browser's keyring entry
  16. Enhancing the sandbox in a distro that doesn't enable the ptrace_scope=1 by default

1. Installing Distrobox and Podman

The command will differ based on your specific package manager. Refer to your distro's docs. For example, on Arch based distros:

sudo pacman -S distrobox podman
Enter fullscreen mode Exit fullscreen mode

2. Configure Distrobox to use Podman

echo 'container_manager="podman"' > ~/.config/distrobox/distrobox.conf
Enter fullscreen mode Exit fullscreen mode

3. Create a container

I use the official container image from CachyOS because it has x86-64-v3 repositories configured out of the box. Moreover, it doesn't have any issues with proprietary codecs, as all the codecs, free and proprietary ones, are available on the main Arch repositories.

It's also possible to use x86-64-v3 packages with openSUSE image (the Distrobox variant) if you install patterns-glibc-hwcaps-x86_64_v3 then sudo zypper dup. The only reason to use openSUSE image rather than CachyOS image is when the browser you want to use doesn't have an official build for Arch based distros, e.g. Google Chrome, Microsoft Edge.

The problem with openSUSE image is also depended on your GPU. If you're using Intel or NVIDIA GPU, it doesn't matter, as Intel relies on intel-media-driver, and NVIDIA relies on libva-nvidia-driver for VA-API.

Unfortunately, if you're with AMD, and for some reason, hurt yourself by not using Brave ๐Ÿ˜‚, you will have to add PackMan repo in openSUSE image. The problem happens when you upgrade the container, it will be conflicted with the main repo from time to time, except now it will happen in your container instead of it happening directly on your system. It's in a way better, but not ideal.

Here's a summary of how to choose which container image to use:

  • Intel and NVIDIA users: Choose based on whether your browser has the official build for the container image, e.g. Brave users use CachyOS image, Google Chrome and Microsoft Edge users use openSUSE image.

  • AMD users: Use CachyOS image + Brave, and call it a day. Otherwise, use openSUSE image + PackMan repo (more on that later).

Now, it's time to actually create a container ๐Ÿ˜

CachyOS Image

distrobox-create -i docker.io/cachyos/cachyos-v3:latest -n browser-v3-dbx -H ~/distrobox/browser-v3-dbx --volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket
Enter fullscreen mode Exit fullscreen mode

openSUSE Image

distrobox-create -i registry.opensuse.org/opensuse/distrobox:latest -n browser-v3-dbx -H ~/distrobox/browser-v3-dbx --volume /run/dbus/system_bus_socket:/run/dbus/system_bus_socket
Enter fullscreen mode Exit fullscreen mode

Then, enter the container with distrobox enter browser-v3-dbx and follow the next step.

Note: For NVIDIA users, in order to use the GPU inside the container, you need to add --nvidia flags to the distrobox-create command. See more here.


4. Enable x86-64-v3 packages

Depends on the container image you choose...

CachyOS Image

Update all the packages:

sudo pacman -Syu
Enter fullscreen mode Exit fullscreen mode

Reinstall all the packages from CachyOS repos (this will replace x86-64 AKA x86-64-v1 packages with x86-64-v3 ones):

sudo pacman -Qqn | sudo pacman -S -
Enter fullscreen mode Exit fullscreen mode

openSUSE Image

Update all the packages:

sudo zypper dup
Enter fullscreen mode Exit fullscreen mode

Enable x86-64-v3 packages:

sudo zypper install patterns-glibc-hwcaps-x86_64_v3
Enter fullscreen mode Exit fullscreen mode

5. Install required packages

CachyOS Image

sudo pacman -S dbus xdg-utils glib2 pipewire adwaita-icon-theme adwaita-cursors adwaita-fonts watchexec
Enter fullscreen mode Exit fullscreen mode

openSUSE Image

sudo zypper install dbus-1 dbus-1-x11 xdg-utils glib2 pipewire adwaita-icon-theme adwaita-fonts watchexec
Enter fullscreen mode Exit fullscreen mode

6. Install GPU driver

pacman command for CachyOS Image, zypper for openSUSE Image.

Intel

sudo pacman -S intel-media-driver libva-utils
Enter fullscreen mode Exit fullscreen mode
sudo zypper install intel-media-driver libva-utils
Enter fullscreen mode Exit fullscreen mode

AMD

sudo pacman -S Mesa libva libva-utils
Enter fullscreen mode Exit fullscreen mode

For openSUSE image, adding PackMan repo first:

sudo zypper ar -f https://ftp.gwdg.de/pub/linux/misc/packman/suse/openSUSE_Tumbleweed/ packman
Enter fullscreen mode Exit fullscreen mode

Refresh the repos:

sudo zypper refresh && sudo zypper dup
Enter fullscreen mode Exit fullscreen mode

Install the driver:

sudo zypper install --from packman Mesa-libva libva2 libva-utils
Enter fullscreen mode Exit fullscreen mode

NVIDIA

sudo pacman -S libva-nvidia-driver libva-utils
Enter fullscreen mode Exit fullscreen mode
sudo zypper install libva-nvidia-driver libva-utils
Enter fullscreen mode Exit fullscreen mode

7. Prepare directories for PWAs

mkdir -p ~/.local/share/applications
mkdir -p ~/Desktop
Enter fullscreen mode Exit fullscreen mode

8. Symlink with the user directories on the host

ln -s /var/home/archerallstars/.local/share/icons/hicolor ~/.local/share/icons/
ln -s /var/home/archerallstars/.local/share/fonts ~/.local/share/
Enter fullscreen mode Exit fullscreen mode

Note: Replace /var/home/archerallstars with your home directory's absolute path on your system (the path you can copy from your file manager, the path without ~).

(optional) Symlink with fontconfig directory on the host

This is usually necessary for multilingual individuals in order to change the fonts for specific languages.

ln -s /var/home/archerallstars/.config/fontconfig/conf.d ~/.config/fontconfig/
Enter fullscreen mode Exit fullscreen mode

9. Install your browser

Please refer to your browser installation instruction. It's the same regardless of the containerization. For example, Brave in CachyOS container:

sudo pacman -S yay
yay -Sy brave-bin
Enter fullscreen mode Exit fullscreen mode

10. Export the browser to the host

For example, for Brave:

distrobox-export - a brave
Enter fullscreen mode Exit fullscreen mode

Note: The actual name of the browser could be different. For example, if you're using CachyOS image, Brave uses brave for its binary, while it's brave-browser when installing from the official openSUSE repo ๐Ÿ˜‚

Try catching this with ls /usr/bin | grep brave.

After the export, you can exit from the container and forget it with exit.


11. Create a script to run the browser with hardware acceleration flags

Unfortunately, this is still necessary in 2025. So, create a file called brave-vaapi or something, put it in ~/.local/bin on your host. Here are the scripts depending on your GPU vendor:

Intel

#!/bin/bash
exec /usr/bin/distrobox-enter  -n browser-v3-dbx  --   /usr/bin/brave --enable-features=AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL,AcceleratedVideoEncoder "$@"
Enter fullscreen mode Exit fullscreen mode

AMD

#!/bin/bash
exec /usr/bin/distrobox-enter  -n browser-v3-dbx  --   /usr/bin/brave --enable-features=AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL,AcceleratedVideoEncoder,VaapiIgnoreDriverChecks "$@"
Enter fullscreen mode Exit fullscreen mode

NVIDIA

#!/bin/bash
exec /usr/bin/distrobox-enter  -n browser-v3-dbx  --   /usr/bin/brave --enable-features=AcceleratedVideoDecodeLinuxZeroCopyGL,AcceleratedVideoDecodeLinuxGL,AcceleratedVideoEncoder,VaapiIgnoreDriverChecks,VaapiOnNvidiaGPUs "$@"
Enter fullscreen mode Exit fullscreen mode

Save the file, right-click > properties and make it executable/run as program.

Note: The reason I use the script to add these flags because these flags could change at any time. Instead of changing them in all the desktop files, changing here would take a much lesser effort ๐Ÿ˜…


12. Modify the browser desktop file that we previously exported on the host

It's in ~/.local/share/applications/. The desktop file could be browser-v3-dbx-brave-browser.desktop.

Open it with your text editor, then search for exec, replace its value to your script from the previous step, like this:

Exec=/var/home/archerallstars/.local/bin/brave-vaapi  %U
Enter fullscreen mode Exit fullscreen mode

Search for all lines that starts with Exec=, replace them with your script, but keep its flags intact (if there's any), like:

Exec=/var/home/archerallstars/.local/bin/brave-vaapi --incognito
Enter fullscreen mode Exit fullscreen mode

Note: Remember to use the absolute path!


13. Fixing the PWAs issue in the container

There's one serious issue that would prevent us from the seamless integration, though. That's the broken PWAs icons, as the icons created in the container would refer to their executable path in the container, of which doesn't exist on the host side.

Moreover, it also has the same issue with every Chromium browser on Flathub when running in native Wayland mode.

However, you can both of the issues easily with a simple bash script, called it brave-pwa-fix and save it in ~/.local/bin on your host.

#!/bin/bash

yourBrowser="brave"
browserDesktopFilename="browser-v3-dbx-brave-browser.desktop"
binPath="/var/home/archerallstars/.local/bin/brave-with-video-accelerated"
hostAppsPath="/var/home/archerallstars/.local/share/applications"
containerAppsPath="/var/home/archerallstars/distrobox/browser-v3-dbx/Desktop"

process_file() {
    local file="$1"
    if [[ ! -f "$file" ]]; then
        return
    fi
    local temp_file
    temp_file=$(mktemp)
    local changed=0
    local icon_value=""

    # First pass to get icon_value
    while IFS= read -r line; do
        if [[ $line =~ ^Icon= ]]; then
            icon_value="${line#Icon=}"
        fi
    done <"$file"

    # Second pass to process
    while IFS= read -r line; do
        if [[ $line =~ ^Name= ]]; then
            if [[ $line == *" ("* ]]; then
                line="${line%% (*}"
                changed=1
            fi
        elif [[ $line =~ ^Exec= ]]; then
            local exec_value="${line#Exec=}"
            if [[ "$exec_value" == *" --"* ]]; then
                local suffix="${exec_value#* --}"
                line="Exec=$binPath --$suffix"
            else
                line="Exec=$binPath"
            fi
            changed=1
        elif [[ $line =~ ^StartupWMClass= ]] && [[ -n "$icon_value" ]]; then
            current_value="${line#StartupWMClass=}"
            if [[ "$current_value" != "$icon_value" ]]; then
                line="StartupWMClass=$icon_value"
                changed=1
            fi
        fi
        echo "$line" >>"$temp_file"
    done <"$file"

    if [[ $changed -eq 1 ]]; then
        cp "$temp_file" "$hostAppsPath/$(basename "$file")"
    fi
    rm "$temp_file"
}

# Process missing files from container
find "$containerAppsPath" -name "*$yourBrowser*.desktop" 2>/dev/null | while read -r file; do
    basename=$(basename "$file")
    host_file="$hostAppsPath/$basename"
    if [[ ! -f "$host_file" ]]; then
        process_file "$file"
    fi
done

# Synchronize: remove orphaned files from hostAppsPath
find $hostAppsPath -name "*$yourBrowser*.desktop" 2>/dev/null | while read -r host_file; do
    basename=$(basename "$host_file")
    if [[ "$basename" == "$browserDesktopFilename" ]]; then
        continue
    fi
    container_file="$containerAppsPath/$basename"
    if [[ ! -f "$container_file" ]]; then
        rm -f "$host_file"
    fi
done
Enter fullscreen mode Exit fullscreen mode

Save the file, right-click > properties and make it executable/run as program.

Explaination

You only need to change these variables values at the top of the script to match the one on your system:

  • yourBrowser andbrowserDesktopFilename should be obvious enough ๐Ÿ˜‚

  • binPath is the path of the script you use to launch the browser with hardware accelerated flags above.

  • hostAppsPath is the path of the user desktop files on the host.

  • containerAppsPath is one of the paths of the user desktop files in the container where PWAs desktop files reside.

Using watchexec to sync PWAs from inside the container to the host and vice versa

This script already handled everything. We just need a watcher that we already installed in step #5, watchexec. We'll run this small yet powerful tool using a systemd service, in which we'll autostart it after user login (with some delay - container safe).

I will go quickly with nano. By now, you should be familar with what we're doing now ๐Ÿ˜

nano ~/.config/systemd/user/pwa-watcher.service
Enter fullscreen mode Exit fullscreen mode

Inside the service file:

[Unit]
Description=Start PWA watcher in the background for the containerized browser
RequiresMountsFor=/run/user/1000/containers

[Service]
Type=exec
ExecStart=/usr/bin/distrobox-enter  -n browser-v3-dbx  --   bash -c 'watchexec -n --watch ~/Desktop distrobox-host-exec /var/home/archerallstars/.local/bin/brave-pwa-fix'
Enter fullscreen mode Exit fullscreen mode

Note: Replace my host's absolute path with your host's absolute path.

nano ~/.config/systemd/user/pwa-watcher.timer
Enter fullscreen mode Exit fullscreen mode

Inside the timer file:

[Unit]
Description=Start PWA watcher service with some delay.

[Timer]
OnStartupSec=23
RandomizedDelaySec=12
Persistent=true

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

Reload and enable the timer, also start the service if you will:

systemctl --user daemon-reload
systemctl --user enable pwa-watcher.timer
systemctl --user start pwa-watcher
Enter fullscreen mode Exit fullscreen mode

14. Auto-update the container to which will also update the browser and everything inside with zero maintenance

We will use a systemd service and its timer agian. Not only that it will update the container that we created, if you have more fun with more containers, all of them will also be updated in the background.

There's a caveat, though. A misconfigured container would stop the update process of all containers after it, so make sure you're doing it right.

nano ~/.config/systemd/user/dbx-upgrade.service
Enter fullscreen mode Exit fullscreen mode

Inside the service file:

[Unit]
Description=Upgrade all Distrobox containers
RequiresMountsFor=/run/user/1000/containers
StartLimitBurst=3
StartLimitIntervalSec=600

[Service]
Type=exec
ExecStart=/usr/bin/distrobox-upgrade --all
Restart=on-failure
RestartSec=60
Enter fullscreen mode Exit fullscreen mode
nano ~/.config/systemd/user/dbx-upgrade.timer
Enter fullscreen mode Exit fullscreen mode

Inside the timer file:

[Unit]
Description=Start Distrobox containers upgrade service with some delay.

[Timer]
OnStartupSec=30
RandomizedDelaySec=15
Persistent=true

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

Reload and enable the timer:

systemctl --user daemon-reload && systemctl --user enable dbx-upgrade.timer
Enter fullscreen mode Exit fullscreen mode

15. Check the browser's keyring entry

After you turning on the sync feature in your browser, it's super important to check whether your setup has created the keyring's entries correctly to ensure you use the uncrompromised encrytion to store your data.

You can check this easily with GNOME Passwords and Keys. It looks dated than Key Rack, but the latter doesn't always find all the entries.

If you did everything right, you will see an entry like this:

Brave Keyring


Is it just easier to go with the Flatpak version if you're using Brave?

Yes, it's. But it has a questionable security implication, thus not recommended by Brave. When it comes to web browser, I want maximum security. Less is not more here, it's unacceptable!

You can check this with the ://sandbox page, for example, in Brave: brave://sandbox.

When running inside the container

Sandbox Status - Container

Simply the same with running the browser natively on your system. Meaning your browser sandbox is not compromised when it's running inside the container.

When running in Flatpak

Sandbox Status - Flatpak

As you can see, the Layer 1 Sandbox is now SUID, and this is without Ptrace Protection with Yama LSM (Non-broker) enabled, of which is available as an alternative mode when namespaces aren't available, see more here and here


16. Enhancing the sandbox in a distro that doesn't enable the ptrace_scope=1 by default

For any users that doesn't debug anything on their PC, enable this security feature shouldn't break anything. It's enabled by default in Ubuntu, Arch, and openSUSE (by my request ๐Ÿ˜„).

Currently, I'm on Fedora Silverblue 42, of which doesn't enable ptrace_scope=1 by default. There's a proposal to enable this by default in Fedora 44, though.

You can check your current ptrace_scope value with:

cat /proc/sys/kernel/yama/ptrace_scope
Enter fullscreen mode Exit fullscreen mode

However, in the meantime, you can enable this easily by copying the default config file and edit it:

sudo cp /usr/lib/sysctl.d/10-default-yama-scope.conf /etc/sysctl.d/
Enter fullscreen mode Exit fullscreen mode
sudo nano /etc/sysctl.d/10-default-yama-scope.conf
Enter fullscreen mode Exit fullscreen mode

The last line should be kernel.yama.ptrace_scope = 1 instead of kernel.yama.ptrace_scope = 0.

Then, reboot. cat /proc/sys/kernel/yama/ptrace_scope again, now, it should return 1. And when you open brave://sandbox/ again, there should be no red entries left (meaning a secure sandbox):

Secure Sandboxing


I hope I don't miss anything. Thanks for reading ๐Ÿ™


Cover Image by Aron Yigin on Unsplash

Top comments (0)