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:
We have n containers each with a VNC server for access, which listens on port 5900.
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:
noVNC: A container with the noVNC web interface is launched, listening on the port
NOVNC_HTTP_PORT
for HTTP and on portNOVNC_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.VNC containers: Containers with VNC servers that run on a port
5900
. The password for each VNC server is passed through environment variables.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.yml
and 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:
Enter the password and get access to the vnc of the container specified in the token parameter.
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.