Skip to content

Repository files navigation

VNC Container Extension

A modular system to add VNC + XFCE4 desktop to ANY existing container image.

What This Does

Wraps any container with:

  • TigerVNC Server - Remote desktop access
  • XFCE4 Desktop - Lightweight GUI environment
  • GPU Support - NVIDIA GPU passthrough for 3D apps
  • Original Functionality Preserved - Container still works as before + VNC

Quick Start

1. Wrap an Existing Image

cd ~/vnc-extension

# Wrap ROS2 Humble
./build-with-vnc.sh \
  --base-image osrf/ros:humble-desktop-full \
  --output ros-humble:vnc

# Wrap your custom image
./build-with-vnc.sh \
  --base-image my-robot:latest \
  --output my-robot:vnc

2. Run with VNC Enabled

# Run with default settings (port 5910)
./run-with-vnc.sh ros-humble:vnc

# Run with custom port and workspace mount
./run-with-vnc.sh my-robot:vnc \
  --port 5911 \
  --workspace ~/my_workspace

3. Connect via VNC

Step 1: Create SSH tunnel

ssh -L 5910:localhost:5910 benz@robovision.csres.utexas.edu

Keep this terminal window open.

Step 2: Connect Screen Sharing on Mac

Open FinderGoConnect to Server (⌘K), then enter:

vnc://localhost:5910

Or use any VNC client and connect to:

localhost:5910

Password: vncpass

Usage Examples

Example 1: ROS2 + RViz

# Build VNC-wrapped ROS2
./build-with-vnc.sh \
  --base-image osrf/ros:humble-desktop-full \
  --output ros-humble:vnc

# Run with workspace mounted
./run-with-vnc.sh ros-humble:vnc \
  --name rviz-container \
  --port 5910 \
  --workspace ~/ros_ws

# On your Mac: Create SSH tunnel
ssh -L 5910:localhost:5910 benz@robovision.csres.utexas.edu

# Connect Screen Sharing to: vnc://localhost:5910
# Password: vncpass

# In VNC desktop, open terminal and run:
# source /opt/ros/humble/setup.bash
# source /root/ros_ws/install/setup.bash
# rviz2

Example 2: Jetson/Isaac ROS Container

# First, build your Jetson container normally
cd ~/cobot_ws/docker
podman build -t cobot:jetson -f Dockerfile .

# Wrap it with VNC
cd ~/vnc-extension
./build-with-vnc.sh \
  --base-image cobot:jetson \
  --output cobot:jetson-vnc

# Run with GPU and workspace
./run-with-vnc.sh cobot:jetson-vnc \
  --port 5911 \
  --workspace ~/cobot_ws

# On your Mac: Create SSH tunnel
ssh -L 5911:localhost:5911 benz@robovision.csres.utexas.edu

# Connect Screen Sharing to: vnc://localhost:5911

Example 3: ML/PyTorch Container

# Wrap official PyTorch image
./build-with-vnc.sh \
  --base-image pytorch/pytorch:latest \
  --output pytorch:vnc

# Run with data directory mounted
./run-with-vnc.sh pytorch:vnc \
  --port 5913 \
  --workspace ~/ml_workspace \
  -- -v ~/datasets:/data:ro

Example 4: Run Without VNC (Normal Container)

# Sometimes you want the image but not VNC overhead
./run-with-vnc.sh my-robot:vnc --disable-vnc

How It Works

Architecture

Original Container (e.g., ROS2 Humble)
         ↓
  VNC Wrapper Build
    (adds TigerVNC + XFCE4)
         ↓
  VNC-Wrapped Container
    ├─ VNC Server (background)
    ├─ XFCE4 Desktop
    └─ Original Container Functionality

Entrypoint Chaining

The VNC wrapper uses a smart entrypoint that:

  1. Starts VNC server in background (if VNC_ENABLED=1)
  2. Chains to original container's CMD/ENTRYPOINT
  3. Container works normally + VNC is available

Files

vnc-extension/
├── Containerfile.vnc-wrapper     # The wrapper Dockerfile
├── entrypoint-vnc-wrapper.sh     # Smart entrypoint
├── build-with-vnc.sh             # Build helper
├── run-with-vnc.sh               # Run helper
└── README.md                     # This file

Advanced Usage

Custom VNC Password

Edit /root/.vnc/passwd inside container or rebuild with custom password:

# In container
podman exec -it my-container bash
echo "mynewpassword" | vncpasswd -f > /root/.vnc/passwd
chmod 600 /root/.vnc/passwd
podman restart my-container

Multiple Containers with Different Ports

# ROS container on :5910
./run-with-vnc.sh ros:vnc --name ros-vnc --port 5910

# ML container on :5911
./run-with-vnc.sh pytorch:vnc --name ml-vnc --port 5911

# Robot container on :5912
./run-with-vnc.sh robot:vnc --name robot-vnc --port 5912

# On Mac, create separate tunnels for each:
ssh -L 5910:localhost:5910 -L 5911:localhost:5911 -L 5912:localhost:5912 benz@robovision.csres.utexas.edu

# Connect to:
# vnc://localhost:5910 (ROS)
# vnc://localhost:5911 (ML)
# vnc://localhost:5912 (Robot)

Manual Run (without script)

podman run -d \
  --name my-container \
  --device nvidia.com/gpu=all \
  -p 5910:5901 \
  -v ~/workspace:/workspace:Z \
  -e VNC_ENABLED=1 \
  my-image:vnc

Disable VNC at Runtime

podman run -d \
  --name my-container \
  -e VNC_ENABLED=0 \
  my-image:vnc

Container Management

# View logs
podman logs -f container-name

# Open shell
podman exec -it container-name bash

# Stop/start
podman stop container-name
podman start container-name

# Remove
podman stop container-name
podman rm container-name

# Remove image
podman rmi image-name:vnc

Troubleshooting

VNC not connecting

First, verify SSH tunnel is active:

# On Mac, check if tunnel is running
ps aux | grep "ssh.*5910"

# Recreate tunnel if needed
ssh -L 5910:localhost:5910 benz@robovision.csres.utexas.edu

Check VNC server status on Linux server:

# Check if VNC is running
podman exec -it container-name ps aux | grep vnc

# Check VNC logs
podman exec -it container-name cat /var/log/vnc.log

# Restart VNC manually if needed
podman exec -it container-name vncserver -kill :1
podman exec -it container-name vncserver :1 -geometry 1920x1080

GPU not working

# Verify GPU is accessible
podman exec -it container-name nvidia-smi

# Check GPU env vars
podman exec -it container-name env | grep NVIDIA

Black screen in VNC

# Check XFCE is installed
podman exec -it container-name which startxfce4

# Check xstartup
podman exec -it container-name cat /root/.vnc/xstartup

# Restart container
podman restart container-name

Why This Design?

✅ Advantages

  1. Universal: Works with ANY base image (ROS, PyTorch, custom)
  2. Non-invasive: Original Dockerfile unchanged, can still use base image
  3. Optional: Can disable VNC at runtime if not needed
  4. Reusable: One wrapper for all projects
  5. Maintainable: Update VNC layer separately from app code

🔄 Alternative: Inline VNC

You could install VNC directly in your Dockerfile:

FROM osrf/ros:humble-desktop-full
RUN apt-get install tigervnc-standalone-server xfce4...

Trade-offs:

  • ❌ Must modify every Dockerfile
  • ❌ Can't use official images without rebuilding
  • ✅ Single image (slightly smaller)

🔄 Alternative: Sidecar Container

Run VNC in separate container sharing display.

Trade-offs:

  • ❌ Complex networking/volume setup
  • ❌ Harder to manage
  • ✅ Complete separation

Contributing

To extend this for different base OSes:

  • Alpine: Replace apt-get with apk
  • Fedora/RHEL: Replace with dnf/yum

License

Free to use and modify.


Created for: Streaming RViz, ROS tools, ML visualization to Mac M3
Server: robovision.csres.utexas.edu
Author: benz

About

A modular system to add VNC + XFCE4 desktop to **ANY** existing container image

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages