45

This question is inspired by Can you run GUI apps in a docker container?.

The basic idea is to run apps with audio and ui (vlc, firefox, skype, ...)

I was searching for docker containers using pulseaudio but all containers I found where using pulseaudio streaming over tcp. (security sandboxing of the applications)

In my case I would prefere playing audio from an app inside the container directly to my host pulseaudio. (without ssh tunneling and bloated docker images)

Pulseaudio because my qt app is using it ;)

6 Answers 6

41

it took me some time until i found out what is needed. (Ubuntu)

we start with the docker run command docker run -ti --rm myContainer sh -c "echo run something"

ALSA:
we need /dev/snd and some hardware access as it looks like. when we put this together we have

docker run -ti --rm \
    -v /dev/snd:/dev/snd \
    --lxc-conf='lxc.cgroup.devices.allow = c 116:* rwm' \
    myContainer sh -c "echo run something"`

In new docker versions without lxc flags you shoud use this:

docker run -ti --rm \
    -v /dev/snd:/dev/snd \
     --privileged \
    myContainer sh -c "echo run something"`

PULSEAUDIO:
update: it may be enought to mount the pulseaudio socket within the container using -v option. this depends on your version and prefered access method. see other answers for the socket method.

Here we need basically /dev/shm, /etc/machine-id and /run/user/$uid/pulse. But that is not all (maybe because of Ubuntu and how they did it in the past). The envirorment variable XDG_RUNTIME_DIR has to be the same in the host system and in your docker container. You may also need /var/lib/dbus because some apps are accessing the machine id from here (may only containing a symbolic link to the 'real' machine id). And at least you may need the hidden home folder ~/.pulse for some temp data (i am not sure about this).

docker run -ti --rm \
    -v /dev/shm:/dev/shm \
    -v /etc/machine-id:/etc/machine-id \
    -v /run/user/$uid/pulse:/run/user/$uid/pulse \
    -v /var/lib/dbus:/var/lib/dbus \
    -v ~/.pulse:/home/$dockerUsername/.pulse \
    myContainer sh -c "echo run something"

In new docker versions you might need to add --privileged.
Of course you can combine both together and use it together with xServer ui forwarding like here: https://stackoverflow.com/a/28971413/2835523

Just to mention:

  • you can handle most of this (all without the used id) in the dockerfile
  • using uid=$(id -u) to get the user id and gid with id -g
  • creating a docker user with this id

create user script:

mkdir -p /home/$dockerUsername && \
echo "$dockerUsername:x:${uid}:${gid}:$dockerUsername,,,:/home/$dockerUsername:/bin/bash" >> /etc/passwd && \
echo "$dockerUsername:x:${uid}:" >> /etc/group && \
mkdir /etc/sudoers.d && \
echo "$dockerUsername ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/$dockerUsername && \
chmod 0440 /etc/sudoers.d/$dockerUsername && \
chown ${uid}:${gid} -R /home/$dockerUsername
10
  • Unfortunately, the --lxc-conf option was removed in 2014. github.com/docker/docker/pull/5797 Commented Nov 2, 2016 at 0:10
  • I have not tested it but I guess there are replacements for the parameter. --privileged, --cap-add, -cgroup-parent and --device could work Commented Nov 2, 2016 at 10:37
  • -v /dev/snd:/dev/snd --privileged works! Check: github.com/pwasiewi/docker-freeciv-client
    – 42n4
    Commented Apr 18, 2017 at 22:57
  • 2
    The ALSA method doesn't work for me. I get the following error ALSA lib pcm_dmix.c:1029:(snd_pcm_dmix_open) unable to open slave.
    – xpilot
    Commented May 8, 2017 at 17:53
  • 1
    Worked for me! However, any luck with Jack Audio?
    – Ignorant
    Commented Oct 11, 2017 at 19:56
11

Nowdays we have a Pipewire and if you want an application inside of the docker container to play / record sounds using your Pipewire server running on a host, here's what you can do:

  1. First of all, we obviously need to have a Pipewire server running on our host. On modern distros the Pipewire is started when systemd reaches graphical.target and login manager (gdm, sddm, whatever) is executed. And then, later on, when the user logs in, the Pipewire is restarted to be owned by that user.

In order for the application inside of docker container to use Pipewire, we're going to pass the socket file descriptor to that container. For that we need to know who is running the Pipewire server on your host machine. You can check that by looking under whom the pipewire processes is running and then find out what will be the proper socket file:

testuser@asrock-ubuntu:~$ ps -fp $(pgrep -d, -x pipewire)

UID          PID    PPID  C STIME TTY          TIME CMD
testuser     4250    4206  1 13:32 ?        00:00:27 /usr/bin/pipewire

testuser@asrock-ubuntu:~$ id testuser

uid=1000(testuser) gid=1000(testuser) groups=1000(testuser),4(adm),6(disk),24(cdrom),27(sudo),30(dip),46(plugdev),105(input),107(kvm),122(lpadmin),134(lxd),135(sambashare),140(libvirt),64055(libvirt-qemu),997(docker)

So in this our case, I expect there should be a socket file /run/user/1000/pipewire-0. Go check that on your system.

  1. Now we are going to create a lightweight Alpine docker container, while passing that Pipewire socket file to it and setting XDG_RUNTIME_DIR variable, which is needed for Pipewire client inside of container.
docker run -it -v /run/user/1000/pipewire-0:/tmp/pipewire-0 -e XDG_RUNTIME_DIR=/tmp --rm alpine /bin/ash
  1. Inside of the container we install some packages into it, to make the whole thing work:
  • alsa-utils - provide a set of ALSA utilities we're going to use as our example software, which needs ALSA
  • pipewire - Pipewire server (and client) binaries. But we're not going to run full-fledged server in a container, not at all, we just need its client part to be there
  • pipewire-alsa - introduces a virtual ALSA device "pipewire" we're going to use in our software, which requires ALSA. Simply speaking that's the bridge between ALSA and our Pipewire client
apk add pipewire-alsa pipewire alsa-utils
  1. Now let's test if everything works. Make sure you see "pipewire" device when you run aplay -L and then run our simple test to play some noise through that device:
speaker-test -Dpipewire -c2

At this point you should hear a sound coming. If it does not, check your default Pipewire sink device on your host. If you're running Gnome, open Gnome Settings -> Sound -> Output

Inside the container, we can now record a wave file with arecord and play it back with using pure ALSA tools:

arecord --duration=5 --device=pipewire test.wav
aplay --device=pipewire test.wav
  1. One last thing, before we go - let's also check that native pipewire clients can be executed inside of that container. For that we install pipewire-tools. The example wav file I'm going to play, came with alsa-utils pacakge we installed earlier:
apk add pipewire-tools
pw-play /usr/share/sounds/alsa/Rear_Center.wav

In a nutshell, the overall setup is something like this:

resulted setup

5
  • I really wanted to up-vote this, but I seem to be unable to mount any socket to my container at all except the xorg-server. They just shows up as a dummy files with ls -l like so: ---------- 1 root root 0 date socket-filename While expecting this: Srw-rw-rw- 1 nobody nobody 0 date socket-filename In this particular case the socket-filename happens to be pipewire-0, but it seems like this issue is happening with all mounted sockets, except X0 even though I use the same syntax for mounting. Is this to be expected? Commented Jul 25, 2023 at 0:34
  • @JoelSahlin is PipeWire running under the same user you use to spin up docker container? Might be privileges issue
    – Alex
    Commented Jul 28, 2023 at 21:43
  • Everything runs under the same user. I managed to mount my sockets using a different syntax for mounting in the config file, which is weird since the first syntax I used is still working for the x-server. I don't understand why it wouldn't work for other sockets as well. Commented Jul 30, 2023 at 0:39
  • @JoelSahlin If the "config file" you're referring to is the docker compose file, then it's likely YAML being weird about particular numbers. Best to specify them as strings (surrounded with quotation marks "like this") instead of raw numbers whenever YAML is involved.
    – haliphax
    Commented Aug 12, 2023 at 16:30
  • I mounted the socket, but trying to play or record audio inside the container just results in "Broken pipe" errors. Audio works on the host. Running container as privileged.
    – Geoff
    Commented Oct 22 at 18:17
8

Inspired by the links you've posted, I was able to create the following solution. It is as lightweight as I could get it. However, I'm not sure if it is (1) secure, and (2) entirely fits your use-case (as it still uses the network).

  1. Install paprefson your host system, e.g. using sudo apt-get install paprefs on an Ubuntu machine.
  2. Launch PulseAudio Preferences, go to the "Network Server" tab, and check the "Enable network access to local sound devices" checkbox [1]
  3. Restart your computer. (Only restarting Pulseaudio didn't work for me on Ubuntu 14.10)
  4. Install Pulseaudio in your container, e.g. sudo apt-get install -y pulseaudio
  5. In your container, run export "PULSE_SERVER=tcp:<host IP address>:<host Pulseaudio port>". For example, export "PULSE_SERVER=tcp:172.16.86.13:4713" [2]. You can find out your IP address using ifconfig and the Pulseaudio port using pax11publish [1].
  6. That's it. Step 5 should probably be automated if the IP address and Pulseaudio port are subject to change. Additionally, I'm not sure if Docker permanently stores environment variables like PULSE_SERVER: If it doesn't then you have to initialize it after each container start.

Suggestions to make my approach even better would be greatly appreciated, since I'm currently working on a similar problem as the OP.

References:
[1] https://github.com/jlund/docker-chrome-pulseaudio
[2] https://github.com/jlund/docker-chrome-pulseaudio/blob/master/Dockerfile

UPDATE (and probably the better solution):
This also works using a Unix socket instead of a TCP socket:

  1. Start the container with -v /run/user/$UID/pulse/native:/path/to/pulseaudio/socket
  2. In the container, run export "PULSE_SERVER=unix:/path/to/pulseaudio/socket"

The /path/to/pulseaudio/socket can be anything, for testing purposes I used /home/user/pulse.
Maybe it will even work with the same path as on the host (taking care of the $UID part) as the default socket, this way the ultimate solution would be -v /run/user/$UID/pulse/native:/run/user/<UID in container>/pulse; I haven't tested this however.

3
  • the idea was to get it working without changing the locale PulseAudio preferences (and without using the network connection). but thanks for the information Commented Mar 27, 2015 at 12:03
  • You also need to set up the pulse cookie so that you are authorized with the host pulseaudio. Basically, -v $HOME/.config/pulse/cookie:/run/pulse/cookie:ro or something. However, changing $PULSE_COOKIE to point to /run/pulse/cookie didn't work for me because the binding is read-only and pulse then falls back to ~/.config/pulse/cookie. So I did ln -fs /run/pulse/cookie ~/.config/pulse/cookie and that's it. Commented May 30, 2016 at 17:49
  • Mapping the pulse cookie wasn't required for me. Perhaps because I was already running the container with the host uid:gid?
    – phs
    Commented Sep 5, 2020 at 7:22
5

After trying most of the solutions described here I found only PulseAudio over network to be really working. However you can make it safe by keeping the authentication.

  1. Install paprefs (on host machine):

    $ apt-get install paprefs
    
  2. Launch paprefs (PulseAudio Preferences) > Network Server > [X] Enable network access to local sound devices.

  3. Restart PulseAudio:

    $ service pulseaudio restart
    
  4. Check it worked or restart machine:

    $ (pax11publish || xprop -root PULSE_SERVER) | grep -Eo 'tcp:[^ ]*'
    tcp:myhostname:4713
    

Now use that socket:

$ docker run \
    -e PULSE_SERVER=tcp:$(hostname -i):4713 \
    -e PULSE_COOKIE=/run/pulse/cookie \
    -v ~/.config/pulse/cookie:/run/pulse/cookie \
    ...

Check that the user running inside the container has access to the cookie file ~/.config/pulse/cookie.

To test it works:

$ apt-get install mplayer
$ mplayer /usr/share/sounds/alsa/Front_Right.wav

For more info may check Docker Mopidy project.

4

Assuming pulseaudio is installed on host and in image, one can provide pulseaudio sound over tcp with only a few steps. pulseaudio does not need to be restarted, and no configuration has to be done on host or in image either. This way it is included in x11docker, without the need of VNC or SSH:

First, find a free tcp port:

read LOWERPORT UPPERPORT < /proc/sys/net/ipv4/ip_local_port_range
while : ; do
  PULSE_PORT="`shuf -i $LOWERPORT-$UPPERPORT -n 1`"
  ss -lpn | grep -q ":$PULSE_PORT " || break
done

Get ip adress of docker daemon. I always find it being 172.17.42.1/16

ip -4 -o a | grep docker0 | awk '{print $4}'

Load pulseaudio tcp module, authenticate connection to docker ip:

PULSE_MODULE_ID=$(pactl load-module module-native-protocol-tcp port=$PULSE_PORT auth-ip-acl=172.17.42.1/16)

On docker run, create environment variable PULSE_SERVER

docker run -e PULSE_SERVER=tcp:172.17.42.1:$PULSE_PORT yourimage

Afterwards, unload tcp module. (Note: for unknown reasons, unloading this module can stop pulseaudio daemon on host):

pactl unload-module $PULSE_MODULE_ID

Edit: How-To for ALSA and Pulseaudio in container

0

I managed to dockerize a Java game in the following ways, effectively passing through the game's sound.

This approach requires building an image, making sure the app has all the dependencies it'll need, in this case, pulseaudio and x11. If you're sure your images has everything it needs, you may procees as stated in the previous answers.

Here, we need to build the image, then we can actually launch it.

docker build -t my-unciv-image . # Run from directory where Dockerfile is
docker run --name unciv # image name\ 
  --device /dev/dri \
  -e DISPLAY=$DISPLAY \
  -e PULSE_SERVER=unix:/run/user/1000/pulse/native \
  --privileged \
  -u $(id -u):$(id -g) \
  -v /path/to/Unciv:/App \
  -v /run/user/$(id -u)/pulse:/run/user/(id -u)/pulse \
  -v /tmp/.X11-unix:/tmp/.X11-unix \
  -w /App \
  my-unciv-image \
  java -jar /App/Unciv.jar

In the second command the following is specified:

  • --name: a name is given to the container
  • --device: video device*
  • -e: required environment vars
    • DISPLAY: the display number
    • PULSE_SERVER: PulseAudio audio server socket
  • --privileged: run ip privileged*, so it can access all devices
  • -v: Mounted volumes:
    • Path to the game mounted into /App in the container**
    • Audio server socke
    • Display server socket
  • -w: Working directory

Here is a docker-compose.yml version of it:

# docker-compose.yml
version: '3'
services:
  unciv:
    build: .
    container_name: unciv
    devices:
      - /dev/dri:/dev/dri # * Either this
    entrypoint: java -jar /App/Unciv.jar
    environment:
      - DISPLAY=$DISPLAY
      - PULSE_SERVER=unix:/run/user/1000/pulse/native
    privileged: true # * or this
    user: 1000:1000
    volumes:
      - /path/to/game/:/App
      - /run/user/1000/pulse:/run/user/1000/pulse
      - /tmp/.X11-unix:/tmp/.X11-unix
    working_dir: /App
FROM ubuntu:20.04

RUN apt-get update
RUN apt-get install openjdk-11-jre -y
RUN apt-get install -y xserver-xorg-video-all
RUN apt-get install -y libgl1-mesa-glx libgl1-mesa-dri
RUN apt-get install -y pulseaudio

USER unciv

Notes:

  • *Only required for a game or anything that uses openGL. Either passing the devices explicitly or running it as privileged, but I think it's enough to pass the device, making it privileged may be overkill.
  • **This math may be bundled with the docker image, but for a demo.
  • For the audio, it's required to pass env variable PULSE_SERVER and mounting the pulseaudio socket

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.