Get Started with Docker - Part 6b: Clientless Remote Desktop with Apache Guacamole
Learn how to deploy Apache Guacamole in Docker behind Cloudflare Tunnel for secure clientless browser RDP and SSH access to homelab servers
In Part 6a, we set up Cloudflare Tunnel (cloudflared) in Docker and learned how to route public hostnames through Cloudflare's edge network with Zero-Trust Email OTP MFA and Geo-IP filtering—without opening a single inbound port on our home router.
Now that our secure remote tunnel foundation is in place, it's time to build on top of it!
In this guide, we're going to deploy Apache Guacamole, an HTML5 clientless gateway that renders full Windows Remote Desktop (RDP), VNC, and Linux SSH sessions directly inside any standard web browser. By putting Guacamole behind your Part 6a Cloudflare Tunnel, you get high-speed, secure remote desktop access to your home lab from any device, anywhere in the world!
We're going to:
- Look at what Apache Guacamole is and how its microservices work
- Set up your persistent Docker Config folders (
Files/AppData/Config/guacamole) - Create our Docker Compose file using Nano Text Editor
- Initialize the required PostgreSQL database schema
- Run and set up Apache Guacamole using Docker Compose
- Connect Guacamole to your Part 6a Cloudflare Tunnel (
remote.yourdomain.com) - Configure your first Remote Desktop (RDP) and SSH connections in Guacamole
- Test accessing your desktops securely inside your web browser
What's Apache Guacamole?
Apache Guacamole is a clientless web-based remote desktop gateway. It runs in a container on your network and renders full RDP (Windows), VNC, and SSH (Linux) sessions right inside any web browser.
That means you can access your home lab servers from a laptop, Chromebook, iPad, or work computer without installing any remote desktop software or VPN clients!
How Apache Guacamole Works (The 3 Microservices)
Under the hood, Apache Guacamole relies on three containerized microservices working in harmony:
-
⚙️
guacd(Guacamole Protocol Proxy Daemon):
The native C daemon (guacamole/guacd) is the protocol translator. It connects directly to your target homelab servers over RDP (port3389), SSH (port22), or VNC (port5900) and converts screen updates into a web-friendly stream. -
🗄️
postgres(Relational Database):
The PostgreSQL container (postgres:15-alpine) stores user credentials, encrypted password hashes, saved connection profiles (target IPs, ports, SSH keys), active session states, and audit logs. -
🖥️
guacamole(Java HTML5 Web Application):
The Java web client (guacamole/guacamole) serves the web UI dashboard in your browser. It handles user authentication against PostgreSQL and passes browser mouse/keyboard events over the internal Docker network toguacd.
💡 Prerequisite Check:
Before starting this guide, ensure you have completed Part 6a: Secure Remote Access with Cloudflare Tunnel to get yourcloudflaredtunnel connector up and running.
Where can I install this?
In this guide, we'll be using our Linux Docker host. You can run the exact same steps on a Raspberry Pi, an Ubuntu server, or inside WSL2 on Windows 11.
Set up your persistent Docker Config folders
In this guide and throughout our Get Started with Docker series, we'll be using the following folder structure to store all of our persistent configuration files:
/home/$yourusername/Files/AppData/Config
It's best practice to keep a structured folder setup to make organizing, updating, and backing up your Docker data volumes easy.
Create our Guacamole Config folders
First, we're going to create folders to mount into our Docker containers so your Guacamole settings, database, and session recordings persist even if containers are updated or recreated.
- SSH into your Docker server installation.
- Check you are in your home folder by running
pwd. - Copy and paste the below command and press Enter to create the required folders:
mkdir -p Files/AppData/Config/guacamole/init Files/AppData/Config/guacamole/data Files/AppData/Config/guacamole/drive Files/AppData/Config/guacamole/record
- Change into the newly created folder:
cd Files/AppData/Config/guacamole
- You should now be in
/home/$yourusername/Files/AppData/Config/guacamole.
Creating our Docker Compose file
We're going to use a Docker Compose file for this deployment. Now that we're in the right folder, create the compose file with the command below:
nano docker-compose.yml
💡 Tip: If you don't have
nanoinstalled on your system, runsudo apt-get install nano.
Copy and paste the below code block into your terminal window:
version: '3.8'
networks:
guac_network:
driver: bridge
services:
# 1. guacd Protocol Proxy Daemon
guacd:
container_name: guacd
image: guacamole/guacd:latest
networks:
- guac_network
restart: always
volumes:
- ./drive:/drive:rw
- ./record:/record:rw
# 2. PostgreSQL Database
postgres:
container_name: guacamole_postgres
image: postgres:15-alpine
environment:
PGDATA: /var/lib/postgresql/data/guacamole
POSTGRES_DB: guacamole_db
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
networks:
- guac_network
restart: always
volumes:
- ./init:/docker-entrypoint-initdb.d:ro
- ./data:/var/lib/postgresql/data:rw
# 3. Guacamole Java Web Application
guacamole:
container_name: guacamole_app
image: guacamole/guacamole:latest
depends_on:
- guacd
- postgres
environment:
GUACD_HOSTNAME: guacd
POSTGRES_DATABASE: guacamole_db
POSTGRES_HOSTNAME: postgres
POSTGRES_USER: guacamole_user
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
networks:
- guac_network
ports:
- "8080:8080" # Local port for web GUI / reverse proxy
restart: always
volumes:
- ./record:/record:rw
Next, create a .env file in the same folder (nano .env) to set your database password:
POSTGRES_PASSWORD=SetYourStrongPasswordHere123!
- Press Ctrl + X, type Y, and press Enter to save the file.
Initializing the PostgreSQL Database Schema
You might wonder why we mapped ./init:/docker-entrypoint-initdb.d in our Docker Compose file.
The official PostgreSQL Docker container has a built-in auto-initialization feature: whenever a fresh database container starts up for the first time, it automatically scans /docker-entrypoint-initdb.d and executes any .sql script placed inside it.
Apache Guacamole provides a built-in helper tool (initdb.sh) that generates all required database tables, indexes, and default admin accounts into a single SQL file.
Run this single command while inside Files/AppData/Config/guacamole before starting your stack:
# Generate the database schema SQL file directly into your ./init folder
docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./init/initdb.sql
Now start up your containers:
docker compose up -d
Docker will start your services. On its very first boot, PostgreSQL detects initdb.sql inside ./init and automatically builds all database tables and default admin credentials for you—no manual SQL commands or database tools required!
❗ Important Security Step:
The default login is usernameguacadminand passwordguacadmin. Log in immediately after setup to change this password!
Connecting Guacamole to your Cloudflare Tunnel (Part 6a)
If you need to deploy the cloudflared connector on your Docker host, here is a brief look at the Docker Compose setup stored in Files/AppData/Config/cloudflared:
version: '3.8'
services:
cloudflared:
container_name: cloudflared
image: cloudflare/cloudflared:latest
restart: unless-stopped
command: tunnel --no-autoupdate run
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN} # Tunnel Token from Cloudflare Zero Trust Dashboard
💡 Full Cloudflare Setup Guide:
For a detailed, step-by-step walkthrough on generating yourTUNNEL_TOKEN, creating Zero-Trust Email OTP MFA rules, bypassing CGNAT, and configuring Geo-IP blocks, check out our complete Part 6a: Secure Remote Access with Cloudflare Tunnel guide!
Now, map your Guacamole service in the Cloudflare Zero Trust Dashboard:
- Log in to your Cloudflare Zero Trust Dashboard.
- Navigate to Networks > Tunnels and edit your
Homelab-Tunnel. - Add a new Public Hostname:
- Public Hostname:
remote.yourdomain.com - Service Type:
HTTP - URL:
http://localhost:8080(orhttp://192.168.0.23:8080)
- Public Hostname:
- Save the hostname.
Because you already configured your Cloudflare Access policy in Part 6a, any request to remote.yourdomain.com is automatically protected by Email OTP MFA and Geo-IP filtering before reaching your Guacamole portal!
🔒 The Cloudflare Tunnel Security & Automatic SSL Bonus
By connecting Apache Guacamole through Cloudflare Tunnel (cloudflared), you instantly gain several major security benefits without adding extra container overhead:
- 💡 Automatic HTTPS / SSL Certificates: Cloudflare automatically issues and manages trusted Universal SSL/TLS certificates for
https://remote.yourdomain.com. You don't need to run Certbot containers, open port 80 for HTTP-01 challenges, or manually renew SSL certificates every 90 days. - 💡 Bypasses CGNAT & Dynamic IPs: Because
cloudflaredmakes an outbound connection from inside your network to Cloudflare, your Guacamole portal remains accessible remotely even if your ISP uses Carrier-Grade NAT (CGNAT) or dynamic WAN IPs—with no expensive Static IP required! - 💡 Zero-Trust Edge MFA: Cloudflare Access blocks unauthorized users at Cloudflare's edge with Email OTP MFA and Geo-IP filtering before anyone can even see your Guacamole login page.
- 💡 Zero Inbound Open Ports: Router ports
3389(RDP),22(SSH), and8080(Guacamole) stay 100% closed to the internet.
Adding your first Remote Connection in Guacamole
Log into your Guacamole web interface (https://remote.yourdomain.com) using guacadmin.
Go to Settings > Connections > Add Connection:
For Windows Server (RDP):
- Name:
Windows Server DC - Protocol:
RDP - Network Hostname:
192.168.0.10(Target local IP) - Port:
3389 - Authentication: Set Security Mode to
NLAand tickIgnore Server Certificate.
For Linux Server (SSH):
- Name:
Ubuntu Host SSH - Protocol:
SSH - Network Hostname:
192.168.0.25 - Port:
22 - Authentication: Enter your SSH Username and paste your SSH Private Key.
Click Save, head back to the Home dashboard, and click your new connection. You will now have a full, high-speed remote desktop or SSH session running right inside your browser tab!
Congratulations! You've combined Cloudflare Zero Trust (Part 6a) with Apache Guacamole (Part 6b) to build a secure, clientless remote desktop gateway powered by Docker!
Don't forget to explore the rest of our website as we build out more content. Stay tuned for more tutorials, tips, and tricks to help you make tech work for you.
If you want to stay up-to-date with regular updates, make sure to subscribe to our free mailing list.