How to securely access Docker containers via VNC using noVNC, websockify and SSL/TLS

In this article I will tell you how you can organize access to multiple Docker containers via VNC using noVNC, websockify And SSL/TLS to encrypt and secure connections. Let's add password protection for each container and enable traffic encryption.

Problem

My project had several Docker containers with graphical applications that had to be connected to remotely via VNC. As long as there were several containers and they were created manually, it was not difficult to allocate separate ports for them to expose outside of the containers and register them in the VNC client. But with the development of the project, containers had to be created dynamically and in different quantities, which made access to them on different ports inconvenient and confusion began with connections already created in the VNC client. I wanted to choose a more convenient option for connections and this is what happened.

Initially, the task seemed simple: launch several containers with VNC servers and access them through a proxy, for example Nginx or Traefik. However, the problem of connecting to all containers through one interface with the ability to route requests to specific containers by URL using Nginx or Traefik could not be solved. Previously, I had seen references/descriptions of noVNC many times, but never resorted to using it; it turned out that in my case it was a completely suitable solution.

Solution: noVNC and websockify

Let me formulate the task:

  1. We have n containers each with a VNC server for access, which listens on port 5900.

  2. I would like not to bother with allocating a separate external port to each container, but instead access each of them using a separate URL.

To solve the problem we use noVNC – a web client for working with VNC via a browser, and websockifywhich will route requests to various VNC servers inside Docker containers. This will allow each container to be accessed via a unique URL and secure connections using SSL/TLS. The noVNC server itself will also be in the container.

Further in the article is an example of setting up noVNC, which everyone can adapt for themselves.


Step 1: Setting up Docker Compose

Let's start by setting up Docker Compose, which will deploy several containers with VNC servers (container1, container2, container3) and provide access to them through the noVNC web interface with routing requests through websockify (novnc container).

Example Docker Compose file:

services:
  novnc:
    image: theasp/novnc  # noVNC образ
    ports:
      - "${NOVNC_HTTP_PORT}:8080"  # Порт для веб-интерфейса noVNC
      - "${NOVNC_HTTPS_PORT}:8443" # Порт для HTTPS с SSL/TLS
    volumes:
      - ./vnc-key.pem:/etc/ssl/private/vnc-key.pem:ro  # Приватный ключ (только для чтения)
      - ./vnc-cert.pem:/etc/ssl/certs/vnc-cert.pem:ro  # Сертификат
    environment:
      - VNC_PASSWORD=${VNC_PASSWORD}
    command: >
      sh -c 'mkdir -p /etc/websockify &&
      echo "pushkin: container1:5900" > /etc/websockify/tokens &&
      echo "dostoevsky: container2:5900" >> /etc/websockify/tokens &&
      echo "chekhov: container3:5900" >> /etc/websockify/tokens &&
      /usr/bin/websockify --web /usr/share/novnc --cert=/etc/ssl/certs/vnc-cert.pem --key=/etc/ssl/private/vnc-key.pem 0.0.0.0:8443 --token-plugin TokenFile --token-source /etc/websockify/tokens'
    networks:
      - test-net

  container1:
    image: dorowu/ubuntu-desktop-lxde-vnc  # Образ для контейнера 1 с VNC-сервером
    expose:
      - "5900"
    environment:
      - VNC_PASSWORD=${VNC_PASSWORD}  # Использование пароля для доступа к VNC
    networks:
      - test-net
    container_name: container1

  container2:
    image: dorowu/ubuntu-desktop-lxde-vnc  # Образ для контейнера 2 с VNC-сервером
    expose:
      - "5900"
    environment:
      - VNC_PASSWORD=${VNC_PASSWORD}
    networks:
      - test-net
    container_name: container2

  container3:
    image: dorowu/ubuntu-desktop-lxde-vnc  # Образ для контейнера 3 с VNC-сервером
    expose:
      - "5900"
    environment:
      - VNC_PASSWORD=${VNC_PASSWORD}
    networks:
      - test-net
    container_name: container3

networks:
  test-net:

Description:

  1. noVNC: A container with the noVNC web interface is launched, listening on the port NOVNC_HTTP_PORT for HTTP and on port NOVNC_HTTPS_PORT for HTTPS. To files /etc/ssl/private/vnc-key.pem And /etc/ssl/certs/vnc-cert.pem the private key and certificate for SSL/TLS encryption are mounted.

  2. VNC containers: Containers with VNC servers that run on a port 5900. The password for each VNC server is passed through environment variables.

  3. Routing via websockify: Requests are routed to the desired containers via websockify using tokens.

Pay attention to the command that generates a list of tokens and saves them to a file for configuring websockify. In the example, this is done for simplicity; in a real project, you can save tokens in other ways. Tokens determine where the web request will be redirected using websockify.

  command: >
    sh -c 'mkdir -p /etc/websockify &&
    echo "pushkin: container1:5900" > /etc/websockify/tokens &&
    echo "dostoevsky: container2:5900" >> /etc/websockify/tokens &&
    echo "chekhov: container3:5900" >> /etc/websockify/tokens &&
    /usr/bin/websockify --web /usr/share/novnc --cert=/etc/ssl/certs/vnc-cert.pem --key=/etc/ssl/private/vnc-key.pem 0.0.0.0:8443 --token-plugin TokenFile --token-source /etc/websockify/tokens'

Step 2: Password protection

To protect VNC servers with a password, use an environment variable VNC_PASSWORD. The password can be stored in .env file or via Docker Secrets for safer use.

Using the .env file:

Create a file .env in the same directory where it is located docker-compose.ymland add variables with passwords and ports for accessing noNVC to it:

VNC_PASSWORD=your_secure_password
NOVNC_HTTP_PORT=8080
NOVNC_HTTPS_PORT=443  # Порт по умолчанию для https

In the Docker Compose file we use a variable to configure the VNC server password:

environment:
  - VNC_PASSWORD=${VNC_PASSWORD}

Step 3: SSL/TLS for Encryption

To secure VNC traffic, we add SSL/TLS certificates to the noVNC container, which will work over HTTPS.

Generating certificates

You can generate a self-signed certificate using OpenSSL:

openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
    -keyout vnc-key.pem -out vnc-cert.pem \
    -subj "/C=US/ST=My Lovely Town/L=My Lovely Town/O=Example Corp/OU=IT Department/CN=example.com"

Mounting certificates into a container

The generated certificates are mounted in the noVNC container with a read-only flag to prevent them from being modified:

volumes:
  - ./vnc-key.pem:/etc/ssl/private/vnc-key.pem:ro
  - ./vnc-cert.pem:/etc/ssl/certs/vnc-cert.pem:ro

Step 4: Connecting to Containers

After starting the containers with the command:

docker compose up -d

You can connect to containers via a browser via HTTPS with or without specifying a port, if the default port 443 for htps is open to the outside:

https://my_host/vnc.vnc.html?path=websockify?token=...

Instead of dots, specify the token of the desired container to connect to it. In our example:

pushkin = container1
dostoevsky = container2
chekhov = container3

Connecting with a self-signed certificate will stress the browser a little and it will doubt whether it is worth connecting further. You don't have to worry and let him do it. The following picture will open in the window:

Connection page

Connection page

Enter the password and get access to the vnc of the container specified in the token parameter.

Entering your password

Entering your password

Selected container screen

Selected container screen


For convenience, a small script that will generate certificates and help manage Docker Compose:

#!/bin/bash

CERT_KEY="vnc-key.pem"
CERT_CRT="vnc-cert.pem"

generate_certificate() {
    echo "Генерация SSL-сертификата..."
    openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 \
        -keyout "${CERT_KEY}" -out "${CERT_CRT}" \
        -subj "/C=US/ST=My Lovely Town/L=My Lovely Town/O=Example Corp/OU=IT Department/CN=example.com"
    echo "Сертификат сгенерирован."
}

start_compose() {
    echo "Запуск Docker Compose..."
    docker-compose up -d
}

stop_compose() {
    echo "Остановка Docker Compose..."
    docker-compose down
}

restart_compose() {
    echo "Перезапуск Docker Compose..."
    docker-compose down
    docker-compose up -d
}

echo "1. Генерация сертификата"
echo "2. Запуск Docker Compose"
echo "3. Остановка Docker Compose"
echo "4. Перезапуск Docker Compose"
read -p "Выберите действие (1-4): " choice

case $choice in
    1) generate_certificate ;;
    2) start_compose ;;
    3) stop_compose ;;
    4) restart_compose ;;
    *) echo "Неверный выбор." ;;
esac

Security assessment

Just a few words about security; my project did not have high requirements, because all containers were on a closed network. And they were accessed via an ssh connection to a port forwarder or vpn. In addition, the security requirements in the project itself were not very high. If you need to have well-protected access, you should better study this connection option. But I can give a few general recommendations:

Password protection

SSL/TLS

  • Self-signed certificates can be used for testing, but for production environments it is still recommended to use certificates from trusted certificate authorities (for example, Let's Encrypt).

Access restriction

  • Limit ports and networks: Make sure that access to VNC servers and WebSocket ports is open only to trusted users or via VPN.


Conclusion

By using noVNC, websockifyand Docker managed to conveniently organize access to multiple containers via VNC with password protection and SSL/TLS encryption.

This approach helped me in organizing connections available through a single interface.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *