Running Firefox Nightly in a container

Published on

This post explains how to run GUI applications in containers, using Firefox Nightly as an example.

My primary browser on desktop these days is Waterfox, installed via Flathub, and it also has an Android app. I like that these have AI-related features disabled by default. Perhaps I’m just an old-fashioned person who can’t keep up with human progress, but for now, I still can’t bring myself to like the situation where AI features are being shoehorned into everything.

Ladybird is another interesting candidate worth mentioning, but as it’s in a pre-alpha state, it doesn’t seem suitable for everyday use yet.


There is an image format called JPEG XL. After Chromium dropped support for it, they recently re-added it, whereas Waterfox already supports it. Looking at the support matrix (Can I use…), among widely used browsers, it seems that only Safari currently supports JPEG XL. Before building this blog, I decided to check whether the image format displays correctly in Firefox.

Back in 2023, there was an announcement that an APT repository providing Firefox Nightly had been set up. And just a few weeks ago, there was a post stating that packages for RPM-based systems had also been provided. However, as expected, it hasn’t been packaged for the musl-based Void Linux I use. Building the browser locally seemed a bit of a hassle, so I decided to run it in a container instead. I’ll explain the steps in the next section.

Run a graphical browser with sound using podman

Prerequisites

The following assumes that Podman is used for container management and that the podman-compose command is available.

zsh
podman compose version
podman-compose version 1.5.0
podman version 5.7.1

As this assumes the use of a Wayland compositor, the environment variables XDG_RUNTIME_DIR and WAYLAND_DISPLAY must be set as shown below. The output may differ from the example.

zsh
printf '%s\n' ${XDG_RUNTIME_DIR:?} ${WAYLAND_DISPLAY:?}
/run/user/1000
wayland-1

For audio output, ensure that files $XDG_RUNTIME_DIR/pulse/native and $XDG_CONFIG_HOME/pulse/cookie are present. These can be created by pipewire-pulse.

Files

Create an empty directory and save the following content as Containerfile.

Containerfile
FROM docker.io/debian:trixie-slim
ARG MOZILLA_KEY_PATH=/etc/apt/keyrings/mozilla.asc
ARG MOZILLA_KEY_DIGEST=sha256:3ecc63922b7795eb23fdc449ff9396f9114cb3cf186d6f5b53ad4cc3ebfbb11f
ADD \
--checksum=${MOZILLA_KEY_DIGEST:?} \
--chmod=644 \
https://packages.mozilla.org/apt/repo-signing-key.gpg \
${MOZILLA_KEY_PATH:?}
COPY \
<<-EOF /etc/apt/sources.list.d/mozilla.sources
Types: deb
URIs: http://packages.mozilla.org/apt
Suites: mozilla
Components: main
Signed-By: ${MOZILLA_KEY_PATH:?}
EOF
RUN \
--mount=type=cache,sharing=locked,target=/var/cache/apt \
--mount=type=cache,sharing=locked,target=/var/lib/apt \
set -eux; \
mv -v /etc/apt/apt.conf.d/docker-clean /tmp/; \
apt-get update; \
apt-get -y satisfy --no-install-recommends \
firefox-nightly \
libpulse0 \
; \
mv -v /tmp/docker-clean /etc/apt/apt.conf.d/; \
:
ENTRYPOINT ["firefox-nightly"]

In the same directory, save the following as compose.yaml.

compose.yaml
---
# yaml-language-server: $schema=https://raw.githubusercontent.com/compose-spec/compose-spec/refs/heads/main/schema/compose-spec.json
x-vars:
environment:
- &env-wayland-display null
- &env-xdg-runtime-dir /tmp
volumes:
- &volume-user-config-mozilla
source: user-config-mozilla
target: /root/.config/mozilla
type: volume
- &bind-pulse-cookie
read_only: true
source: ${XDG_CONFIG_HOME:-~/.config}/pulse/cookie
target: /root/.config/pulse/cookie
type: bind
- &bind-pulse-socket
read_only: true
source: ${XDG_RUNTIME_DIR:?}/pulse/native
target: /tmp/pulse/native
type: bind
- &bind-wayland-socket
read_only: true
source: ${XDG_RUNTIME_DIR:?}/${WAYLAND_DISPLAY:?}
target: /tmp/${WAYLAND_DISPLAY:?}
type: bind
services:
firefox-nightly:
build:
context: .
dockerfile: Containerfile
environment:
WAYLAND_DISPLAY: *env-wayland-display
XDG_RUNTIME_DIR: *env-xdg-runtime-dir
volumes:
- *volume-user-config-mozilla
- *bind-pulse-cookie
- *bind-pulse-socket
- *bind-wayland-socket
volumes:
user-config-mozilla: null

Play around with it

In the directory containing the two files, running the following command will open a browser.

zsh
podman compose run --rm firefox-nightly

When you open Firefox Labs, you should find a checkbox to enable JPEG XL.

Screenshot of Firefox Labs configuration pageFirefox LabsFirefox Labs

Enable the above settings and visit JPEG XL Test Page, and you should see JPEG XL being rendered.

Since we’ve also set up the audio, you should be able to play your favorite Taylor Swift music clip with sound.

Practical example

As a practical example, I’ve created an example project for using the container to develop an Astro site.

Clone the repository and launch the containers.

zsh
git clone https://github.com/mocknen/blog-resource.git
cd blog-resource/posts/running-firefox-nightly-in-a-container/example-project
podman compose up

The example webpage on http://node:4321/ will open in the browser inside the container. Once Astro’s development server starts up, reload the page to view it.

Astro supports hot reloading, so you can see changes in real time by editing index.astro.

You can also access it from your host environment’s browser using the following command.

zsh
(port=$(podman compose port node 4321) && xdg-open http://localhost:${port:?})

After the experiment is finished, delete them as follows.

zsh
podman compose down -v

Wrap-up

By default, utilities run in containers cannot access the host’s display. However, as explained in this post, you can handle not only CLI tools but also GUI ones with just a little configuration.

While containers have limitations, once you create an image, it becomes easier to secure a reproducible environment isolated from the host system. This point may find value in web developments where rendering results can vary depending on browser type and version.

Compared to system containers or virtual machines, application containers are lightweight, and I like how easy it is to create them and instantly discard after use.