Description

[ ↑ ]

Apache Guacamole is a clientless remote desktop gateway. It supports standard protocols like VNC, RDP, and SSH. It is called clientless because no plugins or client software are required. Thanks to HTML5, once Guacamole is installed on a server, all you need to access your desktops is a web browser.

It supports RDP, SSH, Telnet and VNC and is the fastest HTML5 gateway I know. Checkout the projects homepage for more information.

This is a small documentation how to run a fully working Apache Guacamole instance behind an nginx reverse proxy using docker-compose.

Hint:
For the advanced users just download this file, extract it and fire up docker-compose within the extracted folder.

Installing docker and docker-compose

[ ↑ ]

docker-compose CentOS: this works well!

curl -L https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version

Make sure you use the latest versions. I had big problems with old versions and their bugs!

Files

[ ↑ ]

All necessary files are bundled within guacamole-compose.tar.gz. It holds the following directory structure:

.
-reset
-docker-compose.yml
-init
  \initdb.sql
-nginx
  \mysite.template
  \nginx.conf

Note:
If you use the files from the bundle you should read the comments inside docker-compose.yml and re-create the initdb.sql file by executing docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./init/initdb.sql because it could be outdated when you read this document.

reset

A small bash script that resets the folder that holds the above files to its original state. **Be careful using it, it will delete all folders and files that were not initially delivered with guacamole-compose.tar.gz. It looks like this:

#!/bin/bash
echo "This will delete your existing database (./data/)"
echo "          delete your recordings        (./record/)"
echo "          delete your drive files       (./drive/)"
echo "          delete your certs files       (./nginx/ssl/)"
echo ""
read -p "Are you sure? " -n 1 -r
echo ""   # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]; then # do dangerous stuff
 chmod +x init
 sudo rm -r -f ./data/ ./drive/ ./record/ ./nginx/ssl/
fi

docker-compose.yml

A standard docker-compose version 2 yaml-file that docker-compose uses to to init and run guacd, postgres, guacamole and nginx with the following content:

####################################################################################
# docker-compose file for Apache Guacamole
# created by PCFreak 2017-06-28
#
# Apache Guacamole is a clientless remote desktop gateway. It supports standard
# protocols like VNC, RDP, and SSH. We call it clientless because no plugins or
# client software are required. Thanks to HTML5, once Guacamole is installed on
# a server, all you need to access your desktops is a web browser.
####################################################################################
#
# What does this file do?
#
# Using docker-compose it will:
#
# - create a network 'guacnetwork' with the 'bridge' driver.
# - create a service 'guacd' from 'guacamole/guacd' connected to 'guacnetwork'
# - create a service 'postgres' (1) from 'postgres' connected to 'guacnetwork'
# - create a service 'guacamole'(2)  from 'guacamole/guacamole/' conn. to 'guacnetwork'
# - create a service 'nginx' (3) from 'nginx' connected to 'guacnetwork'
#
# (1)
#  DB-Init script is in './init/initdb.sql' it has been created executing
#  'docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./init/initdb.sql'
#  once.
#  DATA-DIR       is in './data'
#  If you want to change the DB password change all lines with 'POSTGRES_PASSWORD:' and
#  change it to your needs before first start.
#  To start from scratch delete './data' dir completely
#  './data' will hold all data after first start!
#  The initdb.d scripts are only executed the first time the container is started
#  (and the database files are empty). If the database files already exist then the initdb.d
#  scripts are ignored (e.g. when you mount a local directory or when docker-compose saves
#  the volume and reuses it for the new container).
#
#  !!!!! MAKE SURE your folder './init' is executable (chmod +x ./init)
#  !!!!! or 'initdb.sql' will be ignored!
#
# (2)
#  Make sure you use the same value for 'POSTGRES_USER' and 'POSTGRES_PASSWORD'
#  as configured under (1)
#
# (3)
#  ./nginx/nginx.conf will be mapped read-only into the container at /etc/nginx/nginx.conf
#  ./nginx/mysite.template will be mapped into the container at /etc/nginx/conf.d/mysite.template
#  ./nginx/ssl will be mapped into the container at /etc/nginx/ssl
#  At startup a self-signed certificate will be created. If you want to use your own certs
#  just remove the part that generates the certs from the 'command' section and replace
#  'self-ssl.key' and 'self.cert' with your certificate.
#  To debug nginx replace '&& nginx -g 'daemon off' with '&& nginx-debug -g 'daemon off'
#  nginx will export port 8443 to the outside world, make sure that this port is reachable
#  on your system from the "outside world". All other traffice is only internal.
#
#  You could remove the entire 'nginx' service from this file if you want to use your own
#  reverse proxy in front of guacamole. If doing so, make sure you change the line
#       - 8080/tcp
#   to   - 8080:8080/tcp
#  within the 'guacamole' service. This will expose the guacamole webinterface directly
#  on port 8080 and you can use it for your own purposes.
#
# !!!!! FOR A FULL RESET (WILL ERASE YOUR DATABASE, YOUR FILES, YOUR RECORDS AND CERTS) DO A
# !!!!!  ./reset
#
#
# The initial login to the guacamole webinterface is:
#
#     Username: guacadmin
#     Password: guacadmin
#
# Make sure you change it immediately!
#
# version            date              comment
# 0.1                2017-06-28        initial release
# 0.2                2017-07-18        added more comments to docker-compose.yml
####################################################################################

version: '2.0'

# networks
# create a network 'guacnetwork' in mode 'bridged'
networks:
  guacnetwork:
    driver: bridge

# services
services:
  # guacd
  guacd:
    image: guacamole/guacd
    networks:
      guacnetwork:
    restart: always
    volumes:
    - ./drive:/drive:rw
    - ./record:/record:rw
  # postgres
  postgres:
    environment:
      PGDATA: /var/lib/postgresql/data/guacamole
      POSTGRES_DB: guacamole_db
      POSTGRES_PASSWORD: Vollgasdepp
      POSTGRES_USER: guacamole_user
    image: postgres
    networks:
      guacnetwork:
    restart: always
    volumes:
    - ./init:/docker-entrypoint-initdb.d:ro
    - ./data:/var/lib/postgresql/data:rw

  # guacamole
  guacamole:
    depends_on:
    - guacd
    - postgres
    environment:
      GUACD_HOSTNAME: guacd
      POSTGRES_DATABASE: guacamole_db
      POSTGRES_HOSTNAME: postgres
      POSTGRES_PASSWORD: Vollgasdepp
      POSTGRES_USER: guacamole_user
    image: guacamole/guacamole
    links:
    - guacd
    networks:
      guacnetwork:
    ports:
    ## enable next line if not using nginx
    ##    - 8080:8080/tcp
    ## enable next line when using nginx
    - 8080/tcp
    restart: always

  # nginx
  nginx:
   restart: always
   image: nginx
   volumes:
   - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
   - ./nginx/mysite.template:/etc/nginx/conf.d/mysite.template
   - ./nginx/ssl:/etc/nginx/ssl
   ports:
   - 8443:443
   ##   environment:
   ##   - NGINX_HOST=nginx
   ##   - NGINX_PORT=443
   links:
   - guacamole
   networks:
     guacnetwork:
   # install openssl, create self-signed certificate and run nginx
   command: /bin/bash -c "apt-get -y update && apt-get -y install openssl && openssl req -nodes -newkey rsa:2048 -new -x509 -keyout /etc/nginx/ssl/self-ssl.key -out /etc/nginx/ssl/self.cert -subj '/C=DE/ST=BY/L=Hintertupfing/O=Dorfwirt/OU=Theke/CN=www.createyourown.domain/emailAddress=docker@createyourown.domain' && cp -f -s /etc/nginx/conf.d/mysite.template /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
####################################################################################

init/initdb.sql

This file contains the initial PostgreSQL database structure and will be bumped into PostgreSQL at first start of the PostgreSQL container (postgres). All future starts of the container will not run the script again! The initial initdb.sql file has been created with the following command:

docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./init/initdb.sql

Check this link for more information.

Hint:
Make sure, the folder ./init is executable (chmod +x ./init) or the script will not work.

nginx/mysite.template

A standard nginx configuration file that will be placed in /etc/nginx/conf.d/mysite.template within the nginx container. According to the documentation of the official nginx container it will be read in by nginx as stated here.

### BBB
server {
    listen       443 ssl;
    server_name  localhost;

        ssl_certificate /etc/nginx/ssl/self.cert;
        ssl_certificate_key /etc/nginx/ssl/self-ssl.key;

        ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
        ssl_prefer_server_ciphers on;
        ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
        ssl_ecdh_curve secp384r1;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
        ssl_stapling off;
        ssl_stapling_verify off;
#        resolver 8.8.8.8 8.8.4.4 valid=300s;
#        resolver_timeout 5s;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
    proxy_pass http://guacamole:8080/guacamole/;
    proxy_buffering off;
    proxy_http_version 1.1;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_cookie_path /guacamole/ /;
    access_log off;
    # allow large uploads (default=1m)
    # 4096m = 4GByte
    client_max_body_size 4096m;
}

    #error_page  404              /404.html;

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

}

nginx/nginx.conf

A standard nginx configuration file that will be placed in /etc/nginx/nginx.conf within the nginx container. The nginx server within the nginx container will pick it up as its default configuration. It also makes sure, that any /etc/nginx/conf.d/*.conf file within the container will be included in nginx' runtime configuration.

### AAA
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

How it works alltogether

[ ↑ ]

The first run of the containers should be done manually. Therefor we start the containers by just cd'ing into the directory with the docker-compose.yml file and the other extracted files. Make sure the folder ./init is executable, then execute

docker-compose up

to run the containers manually. At first start docker will pull 4 images

nginx
postgres
guacamole/guacamole
guacamole/guacd

and will then do the following:

  • Create the network guacnetwork in bridged mode
  • Start a container guacdattached to network guacnetwork
  • expose the guacd container folder /tmp/guacamole/test/drive to ./drive
  • expose the guacd container folder /tmp/guacamole/test/record to ./record
  • Start a container postgres attached to the network guacnetwork
  • The folder ./init will be mapped inside the container as /docker-entrypoint-initdb.d
  • The container postgres will be setup with the following values
    PGDATA: /var/lib/postgresql/data/guacamole
    POSTGRES_DB: guacamole_db
    POSTGRES_PASSWORD: Vollgasdepp
    POSTGRES_USER: guacamole_user
  • The container postgres will run the initdb.sql script and setup the initial database
  • The folder /var/lib/postgresql/data/guacamole will be exposed to the host in the folder ./data
  • Start a container nginx using ./nginx/nginx.conf as /etc/nginx/nginx.conf and ./nginx/mysite.template as /etc/nginx/conf.d/mysite.template
  • The container nginx will expose the folder /etc/nginx/ssl as ./nginx/ssl to the host and will contain the later generated self-signed certificate.
  • The container nginx will expose port 8443 to th host and map it internally to port 443 in the container
  • The container nginx will be linked to container guacamole
  • The container guacamole will be started and linked to the container guacd running within guacnetwork
  • The application guacamole within container guacamole will be setup to use the following database connection and configuration:
    GUACD_HOSTNAME: guacd
    POSTGRES_DATABASE: guacamole_db
    POSTGRES_HOSTNAME: postgres
    POSTGRES_PASSWORD: Vollgasdepp
    POSTGRES_USER: guacamole_user
  • The container guacamole will expose port 8080 only internally inside the guacnetwork network

The exact way wit works can be determined by reading docker-compose.yml.

After successful testing you should open a second shell in the same folder where you started the docker-compose and enter

docker-compose down

to shutdown the containers for now.

If everything worked fine you can now start the docker-compose process again, but now we tell docker to run these containers daemonized (-d).

docker-compose up -d

Your containers should now start and guacamole is running. From now on it will start automatically on server startup (with docker daemon).

Upgrade to newer versions

[ ↑ ]

From time to time there will be new versions of Guacamole available. In general an update is no problem, you just stop your containers, delete one or more images and then re-run the containers (latest images will be pulled from internet).

But there is a problem!

Newer versions often change the database schema and when you try to run your containers based on the new images they will fail.

Here is the solution I came up with and it worked for me.

Shutdown Guacamole

user@host /dockers/compose/guacamole> docker-compose down

Enumerate images

First find all images of guacamole with this command

user@host /dockers/compose/guacamole> docker images --filter=reference='guacamole*/*'
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
guacamole/guacd       latest              ac5de9daf9f3        2 months ago        499MB
guacamole/guacamole   latest              b602d8daff1b        2 months ago        660MB

Delete images you want to upgrade

Delete them (use bash docker rmi <IMAGE ID>) or run as single command:

user@host /dockers/compose/guacamole> docker rmi $(docker images --filter=reference='guacamole*/*' -q)
Untagged: guacamole/guacd:latest
Untagged: guacamole/guacd@sha256:358379b75e16e99f44b15e744ac0c958acc872e4510f375a7a7e2a265b2ec7ad
Deleted: sha256:ac5de9daf9f38def1ba96f4926d7923b9df9d3791162b1736a95d3d9c4305576
Deleted: sha256:d59f26605310129a585c684e38c76cd5fe7735febb12aba4de04fb5bf6ff1839
Deleted: sha256:0d6385ada3a8221027817bd4106d6f04af6210ffe12e568159938a17fc6820b4
Deleted: sha256:5066594657365b82de1da81266c057a7750b3d81484f0dcc9a1c69bfa6e776ce
Deleted: sha256:938454d723921cfbda3a01004b73bdf83f2c9bdc44db2f377ee4965a96d89692
Untagged: guacamole/guacamole:latest
Untagged: guacamole/guacamole@sha256:548dfb4dc315d981e2a7f5959fcffdb101fe3924a086a96ad9c94f880b6f313c
Deleted: sha256:b602d8daff1b146c932ec1c89c92824c8ae2fd92fd030206fdaefa292b8cf848
Deleted: sha256:b2d828d2ab2ff993b871f6221bb40a51f5d60e482899d5f397dbae8ffa605517
Deleted: sha256:efeabb36b2830f0e46b532a4a29f9f0b50bc74c3f99ccaab74c76f959302c1b0
Deleted: sha256:e2e1254697ca72ba8312cc7211b4cd0fd86aef7c485839332b3a20f755f8c954

Restart containers (pulling updated images)

Now restart your containers, this will pull down the latest version of the previously deleted images

user@host /dockers/compose/guacamole> docker-compose up -d --remove-orphans
Creating network "guacamole_guacnetwork" with driver "bridge"
Pulling guacd (guacamole/guacd:latest)...
latest: Pulling from guacamole/guacd
...
Status: Downloaded newer image for guacamole/guacd:latest
Pulling guacamole (guacamole/guacamole:latest)...
latest: Pulling from guacamole/guacamole
...
Status: Downloaded newer image for guacamole/guacamole:latest
...
...

Find IP of PostgreSQL container

Now we need to find the internal docker IP address of the PostGreSQL container.

user@host /dockers/compose/guacamole> docker network inspect guacamole_guacnetwork | grep -B1 -A 5 postgres
            "3958c97a3457149d4209d32ea9fd0738a199710ca5d89a65683d05545e9ef601": {
                "Name": "postgres_guacamole_elitearch",
                "EndpointID": "911ac8589a3b397621251d9e72c46c98ebf9a406d52fc083ef1354f479d150d6",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },

In the above example the IP is 172.18.0.2

Start interactive bash shell in guacamole/guacamole

Then we start a bash shell within the guacamole/guacamole container (you need the ID of guacamole/guacamole for this).

user@host /dockers/compose/guacamole> docker exec -i -t 50c0df15649c bash
root@50c0df15649c:/usr/local/tomcat#

Retrieve list of update scripts

List the update scripts:

root@50c0df15649c:/usr/local/tomcat# ls -l /opt/guacamole/postgresql/schema/upgrade/
total 28
-rw-rw-r-- 1 root root 5568 Jun 26 21:29 upgrade-pre-0.9.10.sql
-rw-rw-r-- 1 root root 1575 Jun 26 21:29 upgrade-pre-0.9.11.sql
-rw-rw-r-- 1 root root 1557 Jun 29 18:15 upgrade-pre-0.9.13.sql
-rw-rw-r-- 1 root root 1051 Jun 26 21:29 upgrade-pre-0.9.7.sql
-rw-rw-r-- 1 root root 1733 Jun 26 21:29 upgrade-pre-0.9.8.sql
-rw-rw-r-- 1 root root 1106 Jun 26 21:29 upgrade-pre-0.9.9.sql

For the next step, make sure you have the name of your database, your database user, your database password and the internal docker IP of your postgresql container handy.

Install psql within container

Install psql command line tool within guacamole/guacamole

root@50c0df15649c:/usr/local/tomcat# apt-get update
root@50c0df15649c:/usr/local/tomcat# apt-get install postgresql-client

Use DB update scripts

Execute the update script by connecting to the docker IP of the PostgreSQL container (in my case an upgrade from 0.9.x to 0.9.13:

root@50c0df15649c:/usr/local/tomcat# psql -h 172.18.0.2 -U guacamole_user -W -d guacamole_db -f /opt/guacamole/postgresql/schema/upgrade/upgrade-pre-0.9.13.sql
Password for user guacamole_user: <enter your password here>
CREATE TYPE
ALTER TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE
ALTER TABLE

Now the database is up-to-date and is compatible with the latest guacamole.

Exit the bash prompt by typing exit.

Shutdown containers again

Shutdown the containers completely again

user@host /dockers/compose/guacamole> docker-compose down

Restart containers

Then restart the containers

user@host /dockers/compose/guacamole> docker-compose up -d --remove-orphans

You guacamole should now have the latest version and work!

More details on this procedure can be found here (yellow boxes):
http://guacamole.incubator.apache.org/doc/gug/jdbc-auth.html#jdbc-auth-mysql:

Upgrade PostGres to new major version

[ ↑ ]

At the moment we have to explicitely specify the postgres version in docker-compose.yml

If your update process fails (postgres will not start because of major version changes), try to explicitely set the PostGRESQL version in the docker-compose file:

    image: postgres:9.6.9

NOT YET TRIED
If your update process fails (postgres will not start because of major version changes), try to explicitely set the PostGRESQL version in the docker-compose file:

    image: postgres:9.6.9

You should now be able to start the containers again. But still on old version. So I also added the following line in the postgres section of my docker-compose file to have a separate directory for database backups mapped:

    - ./data_bck:/var/lib/postgresql/data_bck:rw

After this changes start the stuff as usual and open a shell into the postgres container:

docker exec -it bde8872ef8af /bin/bash

Now we need to dump the databases:

pg_dumpall -U postgres > /var/lib/postgresql/data_bck/fullbackup.dump

This will save fullbackup.dump to ./data_bck in the real filesystem. Now exit the container and shutdown the containers.

root@bde8872ef8af:/# exit
user@localmachine> docker-compose down

Now change back

    image: postgres:9.6.9

to

    image: postgres

in docker-compose.yml. Delete the postgres container(s) and rename the data dir to data.old.

Now create a new initdb.sql with

docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > ./init/initdb.sql

Now start guacamole (which will completely recreate an empty guacamole installation)

Again create a shell into the postgres container now

Problems

[ ↑ ]

If you can not connect to port 8443 on your host, make sure your firewall rules are correct and that you have exposed this port to the outside world. Often times it does not help to stop the firewall e.g. firewalld because docker-compose will use firewalld for its internal routing stuff, too.
You have to make sure, that your docker and firewall setup works flawlessly beforehand.