How to Run GUI Applications in Docker (X11 Forwarding and VNC)
Learn how to run graphical applications inside Docker containers using X11 forwarding, VNC servers, and browser-based solutions for cross-platform GUI access.
Running GUI applications in Docker enables isolated desktop environments, reproducible development setups, and testing across different platforms. There are several approaches depending on your host OS and requirements.
X11 Forwarding (Linux Host)
X11 forwarding shares the host’s display server with the container, providing the best performance.
Basic X11 Forwarding
Copy# Allow container to connect to X server xhost +local:docker # Run GUI application docker run -it --rm \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ firefoxSecure X11 Forwarding
Copy# More secure: allow only your local user, then run the container as that user xhost +SI:localuser:$(id -un) docker run -it --rm \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ --user $(id -u):$(id -g) \ firefoxDocker Compose with X11
Copyservices: gui-app: image: my-gui-app environment: - DISPLAY=${DISPLAY} volumes: - /tmp/.X11-unix:/tmp/.X11-unix:rw - ${HOME}/.Xauthority:/root/.Xauthority:ro network_mode: hostCustom Dockerfile for X11
CopyFROM ubuntu:22.04 # Install GUI dependencies RUN apt-get update && apt-get install -y \ libx11-6 \ libxext6 \ libxrender1 \ libxtst6 \ libxi6 \ libfreetype6 \ fontconfig \ && rm -rf /var/lib/apt/lists/* # Install your application RUN apt-get update && apt-get install -y firefox && rm -rf /var/lib/apt/lists/* # Run as non-root for security RUN useradd -m appuser USER appuser CMD ["firefox"]VNC Server in Container
VNC provides remote desktop access, works on any host OS, and offers better isolation.
Basic VNC Setup
CopyFROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive # Install VNC server and desktop environment RUN apt-get update && apt-get install -y \ tigervnc-standalone-server \ tigervnc-common \ dbus-x11 \ xfce4 \ xfce4-goodies \ && rm -rf /var/lib/apt/lists/* # Install your applications RUN apt-get update && apt-get install -y \ firefox \ gedit \ && rm -rf /var/lib/apt/lists/* # Create user RUN useradd -m -s /bin/bash vncuser && \ echo "vncuser:password" | chpasswd USER vncuser WORKDIR /home/vncuser # VNC configuration RUN mkdir -p ~/.vnc && \ echo "password" | vncpasswd -f > ~/.vnc/passwd && \ chmod 600 ~/.vnc/passwd # VNC startup script RUN echo '#!/bin/bash\n\ startxfce4 &\n\ ' > ~/.vnc/xstartup && chmod +x ~/.vnc/xstartup EXPOSE 5901 CMD ["vncserver", "-localhost", "no", "-geometry", "1920x1080", "-depth", "24", ":1", "-fg"]Docker Compose with VNC
Copyservices: vnc-desktop: build: . ports: - "5901:5901" environment: - VNC_PASSWORD=secret volumes: - vnc-home:/home/vncuser shm_size: '256mb' volumes: vnc-home:Connect with any VNC client to
localhost:5901.noVNC (Browser-Based VNC)
noVNC provides VNC access through a web browser-no client installation needed.
Dockerfile with noVNC
CopyFROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive # Install desktop environment and VNC RUN apt-get update && apt-get install -y \ tigervnc-standalone-server \ novnc \ websockify \ xfce4 \ xfce4-terminal \ dbus-x11 \ && rm -rf /var/lib/apt/lists/* # Install applications RUN apt-get update && apt-get install -y \ firefox \ && rm -rf /var/lib/apt/lists/* # Create user RUN useradd -m vncuser USER vncuser WORKDIR /home/vncuser # Setup VNC RUN mkdir -p ~/.vnc && \ echo "password" | vncpasswd -f > ~/.vnc/passwd && \ chmod 600 ~/.vnc/passwd COPY --chown=vncuser:vncuser startup.sh /home/vncuser/ RUN chmod +x /home/vncuser/startup.sh EXPOSE 6080 CMD ["/home/vncuser/startup.sh"]startup.sh:
Copy#!/bin/bash # Start VNC server vncserver -localhost yes -geometry 1920x1080 -depth 24 :1 # Start noVNC /usr/share/novnc/utils/novnc_proxy \ --vnc localhost:5901 \ --listen 6080 # Keep running tail -f /dev/nullDocker Compose with noVNC
Copyservices: desktop: build: . ports: - "6080:6080" environment: - VNC_RESOLUTION=1920x1080 volumes: - desktop-data:/home/vncuser shm_size: '512mb' volumes: desktop-data:Access via browser at
http://localhost:6080.X11 on macOS
macOS doesn’t have a native X11 server. Use XQuartz.
Setup XQuartz
Copy# Install XQuartz brew install --cask xquartz # Start XQuartz and enable network connections # XQuartz -> Preferences -> Security -> "Allow connections from network clients" # Restart XQuartz, then: xhost +localhost # Run container docker run -it --rm \ -e DISPLAY=host.docker.internal:0 \ -v /tmp/.X11-unix:/tmp/.X11-unix \ firefoxDocker Compose for macOS
Copyservices: gui-app: image: my-gui-app environment: - DISPLAY=host.docker.internal:0 volumes: - /tmp/.X11-unix:/tmp/.X11-unixX11 on Windows (WSL2)
With WSL2 and WSLg, X11 applications work natively.
WSLg (Windows 11)
Copy# WSLg provides automatic X11 support docker run -it --rm \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -e WAYLAND_DISPLAY=$WAYLAND_DISPLAY \ -e XDG_RUNTIME_DIR=$XDG_RUNTIME_DIR \ -e PULSE_SERVER=$PULSE_SERVER \ -v /mnt/wslg:/mnt/wslg \ firefoxUsing VcXsrv (Windows 10)
- Install VcXsrv
- Start XLaunch with “Disable access control”
- Run container:
Copy# Get Windows host IP export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0 docker run -it --rm \ -e DISPLAY=$DISPLAY \ firefoxComplete Desktop Environment
Full Ubuntu Desktop
CopyFROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive # Install full desktop RUN apt-get update && apt-get install -y \ ubuntu-desktop \ xfce4 \ xfce4-goodies \ dbus-x11 \ tigervnc-standalone-server \ novnc \ websockify \ && rm -rf /var/lib/apt/lists/* # Clean up RUN apt-get clean # Create user RUN useradd -m -s /bin/bash -G sudo developer && \ echo "developer:developer" | chpasswd USER developer WORKDIR /home/developer # VNC setup RUN mkdir -p ~/.vnc && \ echo "password" | vncpasswd -f > ~/.vnc/passwd && \ chmod 600 ~/.vnc/passwd && \ echo '#!/bin/bash\nstartxfce4 &' > ~/.vnc/xstartup && \ chmod +x ~/.vnc/xstartup EXPOSE 5901 6080 COPY --chown=developer:developer entrypoint.sh /home/developer/ ENTRYPOINT ["/home/developer/entrypoint.sh"]entrypoint.sh:
Copy#!/bin/bash set -e # Start VNC vncserver -localhost yes -geometry ${RESOLUTION:-1920x1080} :1 # Start noVNC /usr/share/novnc/utils/novnc_proxy --vnc localhost:5901 --listen 6080 & # Keep container running exec tail -f /dev/nullDevelopment Environment with IDE
Copyservices: dev-desktop: build: context: . dockerfile: Dockerfile.dev ports: - "6080:6080" # noVNC - "5901:5901" # VNC volumes: - ./workspace:/home/developer/workspace - vscode-extensions:/home/developer/.vscode environment: - RESOLUTION=1920x1080 shm_size: '2gb' volumes: vscode-extensions:Dockerfile.dev:
CopyFROM ubuntu:22.04 ENV DEBIAN_FRONTEND=noninteractive # Desktop environment RUN apt-get update && apt-get install -y \ xfce4 \ xfce4-terminal \ tigervnc-standalone-server \ novnc \ websockify \ dbus-x11 \ && rm -rf /var/lib/apt/lists/* # Development tools RUN apt-get update && apt-get install -y \ ca-certificates \ curl \ git \ gnupg \ sudo \ build-essential \ && rm -rf /var/lib/apt/lists/* # Install VS Code RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor -o /usr/share/keyrings/microsoft.gpg && \ echo "deb [signed-by=/usr/share/keyrings/microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list && \ apt-get update && apt-get install -y code && \ rm -rf /var/lib/apt/lists/* # Create developer user RUN useradd -m -s /bin/bash developer && \ echo "developer ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers USER developer WORKDIR /home/developer # VNC configuration RUN mkdir -p ~/.vnc && \ echo "password" | vncpasswd -f > ~/.vnc/passwd && \ chmod 600 ~/.vnc/passwd && \ echo -e '#!/bin/bash\nstartxfce4 &' > ~/.vnc/xstartup && \ chmod +x ~/.vnc/xstartup COPY --chown=developer:developer entrypoint.sh /home/developer/ EXPOSE 5901 6080 ENTRYPOINT ["/home/developer/entrypoint.sh"]Handling Audio
PulseAudio Forwarding
Copy# On host, allow network audio pactl load-module module-native-protocol-tcp auth-anonymous=1 # Run container with audio docker run -it --rm \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -e PULSE_SERVER=tcp:$(hostname -I | awk '{print $1}'):4713 \ my-audio-appDocker Compose with Audio
Copyservices: multimedia: image: my-multimedia-app extra_hosts: - "host.docker.internal:host-gateway" environment: - DISPLAY=${DISPLAY} - PULSE_SERVER=tcp:host.docker.internal:4713 volumes: - /tmp/.X11-unix:/tmp/.X11-unix - ${HOME}/.config/pulse:/root/.config/pulse:roGPU Acceleration for GUI
OpenGL Support
Copydocker run -it --rm \ --gpus all \ -e DISPLAY=$DISPLAY \ -v /tmp/.X11-unix:/tmp/.X11-unix \ -e NVIDIA_DRIVER_CAPABILITIES=all \ nvidia/opengl:1.2-glvnd-runtime-ubuntu22.04 \ bash -lc "apt-get update && apt-get install -y mesa-utils && glxgears"Dockerfile with GPU and GUI
CopyFROM nvidia/opengl:1.2-glvnd-runtime-ubuntu22.04 RUN apt-get update && apt-get install -y \ mesa-utils \ libgl1-mesa-glx \ && rm -rf /var/lib/apt/lists/* # Your GPU-accelerated GUI application RUN apt-get update && apt-get install -y blender && rm -rf /var/lib/apt/lists/* CMD ["blender"]Troubleshooting
Common X11 Issues
Error Cause Solution cannot open displayX server not accessible Run xhost +local:dockerNo protocol specifiedXauthority issue Mount ~/.XauthorityXlib: extension missingMissing GL libraries Install mesa-utils Connection refusedWrong DISPLAY Check echo $DISPLAYVNC Issues
Error Cause Solution Black screen Desktop not starting Check xstartup script Connection timeout Port not exposed Verify port mapping Authentication failed Wrong password Reset VNC password Slow performance Low color depth Use -depth 24Debug Commands
Copy# Check if X socket exists ls -la /tmp/.X11-unix/ # Verify DISPLAY variable echo $DISPLAY # Test X connection xdpyinfo # Check container logs for VNC docker logs vnc-containerSecurity Considerations
Method Security Level Notes X11 with xhost +local: Low Container can access host X server X11 with Xauthority Medium Better isolation VNC High Network accessible, use strong password noVNC over HTTPS High Add TLS termination Secure noVNC with TLS
Copyservices: desktop: image: vnc-desktop ports: - "6080:6080" nginx: image: nginx:alpine ports: - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./certs:/etc/nginx/certs:ro depends_on: - desktopSummary
Method Best For Host OS X11 Forwarding Performance, Linux hosts Linux XQuartz + X11 macOS development macOS WSLg Windows 11 native Windows 11 VNC Cross-platform, isolation Any noVNC Browser access, no client Any Choose X11 forwarding for best performance on Linux, VNC for cross-platform compatibility and better isolation, and noVNC for easy browser-based access without client installation. For production use cases, always secure VNC connections with strong passwords and TLS.
Nawaz Dhandala is building OneUptime with a passion for engineering reliable systems and improving observability.
A civil engineer with a longlife fondness for Software Libero