This post is a long time in coming. A long time. The goal was to setup ConnectWise Control in a Docker container and use some sort of web proxy with Let's Encrypt to provide SSL encryption over https. There were a lot of new technologies to learn and then there problems, more problems and even more problems on top of that...
...And I solved them all. My hope others do suffer the frustrations I did.
more to come...
...so after I wrote this I proceeded to overwrite my working configuration while trying to do a backup in order to transfer everything over to the new server. Back to the beginning, the third time's the charm.
Step 1: Install Docker
There are plenty of articles on how to install Docker on the Internet. I am using Ubuntu 20.04 LTS has my host system and I used this article from Digital Ocean:
Step 1.5 Install docker-compose
This article from digitalocean explains how to do this quite well:
Step 2: Build ScreenConnect Image
I initially built this ScreenConnect image over a year ago and some of the details are a bit hazy. The original image was based on version 19.4 running on Ubuntu 18.04. Ultimately, I wanted to end up with the latest version of ScreenConnect for Linux running on the latest version of Ubuntu which at the time of writing this article is ScreenConnect 20.2 and Ubuntu 20.04 LTS.
Note: When I refer to ScreenConnect I am referring to the ScreenConnect application. When I refer to screenconnect I am referring to the docker container.
The upgrade process was a source of many problems. The good news is that I figured them out and hopefully this will save you a lot of time.
First I created a directory to hold all the files related to creating the image and container. I called it screenconnect and you can call it whatever works for you.
You will need the following in the screenconnect directory:
- a Dockerfile for building the image (see below)
- a subdirectory 'setup' for holding all the ScreenConnect Installers. I needed 19.4, 19.6 and 20.2 but more on that later.
- supervisord.conf (see below)
Here is the Dockerfile:
FROM ubuntu ENV SC_INSTALL_FILE ScreenConnect_19.4.25759.7247_Release.tar.gz MAINTAINER Max Abramowitz email@example.com RUN apt-get update && apt-get -y upgrade RUN DEBIAN_FRONTEND=noninteractive apt-get -y install mono-runtime ca-certificates-mono supervisor WORKDIR /tmp COPY setup/$SC_INSTALL_FILE /tmp RUN tar -zxvf $SC_INSTALL_FILERUN echo -e "\n\n" | ScreenConnect_*_Install/install.sh RUN DEBIAN_FRONTEND=noninteractive apt-get -y install libc6-dev libz-dev WORKDIR /lib/x86_64-linux-gnuRUN mv libc.so libc.so.oldRUN ln -s libc-2.31.so libc.so EXPOSE 8040 8041 WORKDIR /opt/screenconnect COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
Pretty straight forward. Get the latest Ubuntu image to use as our base image.
ENV SC_INSTALL_FILE ScreenConnect_19.4.25759.7247_Release.tar.gz
Create an ENVironment variable SC_INSTALL_FILE. This makes is relatively easy to build new images as needed. So if you want to build a ScreenConnect image based on version 20.2 you would just replace ScreenConnect_19.4.25759.7247_Release.tar.gz with ScreenConnect_20.2.29488.7513_Release.tar.gz or whatever is appropriate for your needs.
MAINTAINER Max Abramowitz <firstname.lastname@example.org>
Added a maintainer tag to give myself some credit.
RUN apt-get update && apt-get -y upgrade
Again pretty straight forward update and upgrade ubuntu with apt-get.
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install mono-runtime ca-certificates-mono supervisor
Install mono-runtime ca-certificates-mono and supervisor. DEBIAN_FRONTEND=noninteractive tells apt-get to accept all the default answers for all questions (if any).
Set the working directory to /tmp
COPY setup/$SC_INSTALL_FILE /tmp
Copy the installer defined at the top of the file from the setup directory of your host system into the /tmp directory of the image you are building.
RUN tar -zxvf $SC_INSTALL_FILE
Extract the installation files.
RUN echo -e "\n\n" | ScreenConnect_*_Install/install.sh
Send two newline characters to install.sh. This is the equivalent of running the install.sh script and hitting enter/return twice to accept the default values.
RUN DEBIAN_FRONTEND=noninteractive apt-get -y install libc6-dev libz-dev WORKDIR /lib/x86_64-linux-gnu RUN mv libc.so libc.so.old RUN ln -s libc-2.31.so libc.so
This next section of commands is needed for Ubuntu 20.04. It was not necessary in Ubuntu 18.04. If you do not include it the image will build (great!) and ScreenConnect will not run (bad!).
In summary: install libc6-dev and libz-dev, rename libc.so and create a symbolic link.
EXPOSE 8040 8041
Expose port 8040 and 8041
Set the working directory to /opt/screenconnect
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
And finally copy the supervisord.conf from your host system to /etc/supervisor/conf.d/ on your image.
This part of the build I did so long ago I kind of forgot about it. One of the confusing things for me about Docker is that a container is not a virtual machine. Containers do one thing (mostly) and when they are done doing that thing they stop. This is a problem for programs that run as services, because if you try to run them in a Docker container you will find that they run and then terminate which terminates your Docker container. Supervisor is a way to get around this.
[supervisord] nodaemon=true [program:ScreenConnect] command=/etc/init.d/screenconnect start
I have forgotten why this works, but it does.
So now that you have all your files in place it is time to build your image:
docker build -t wmc/sc -t wmc/sc:19.4 .
The -t options are tags. I tagged each image with a version number and left the version number out to indicated that this was the latest image when appropriate.
Building the image will take a minute or two.
Step 3: Create a docker-compose.yml file
We are going to do this in two steps. First we are going to create a docker-compose.yml file that will run our ScreenConnect image just all by its lonesome, then once we are sure that this is working we will proxy traffic with Traefik and encrypt our traffic with Let's Encrypt.
version: '3' services: screenconnect: container_name: sc image: wmc/sc:20.2 restart: unless-stopped volumes: - opt:/opt/screenconnect ports: - 8040:8040 - 8041:8041 volumes: opt:
This is a relatively simple docker-compose.yml file. Creates screenconnect service, with a container named "sc" using the image created in the previous step. Defines a volume to be used by the container and makes ports 8040 and 8041 reachable on the network.
Test that the image is working with:
docker-compose up -d
Tips & Tricks
Monitoring ScreenConnect Startup
ScreenConnect can take a long time to startup. This caused me a huge amount of problems until I was able to properly monitor ScreenConnect's startup process. To do this, first get a shell to the running container:
docker exec -it sc bash
Then, tail -f the screenconnect log file:
tail -f /var/log/screenconnect
You will initially see this:
When ScreenConnect completes it startup process, you will see this:
At this point you should be able to access your running instance of ScreenConnect at http://yourhostname.yourdomain.com:8040.
Migration & Upgrading
At this point you should migrate from an old server and upgrade you screenconnect container.
You cannot copy files from one version of ScreenConnect to another and expect it to work. If you need to migrate from an old version of ScreenConnect start by building an image based on the version of ScreenConnect you are running, migrate your data and settings and then upgrade the container. My recommendation is to validate your work at each step: build the base version and test, migrate data and test, upgrade container and test.
You will need to perform upgrades on the running container (see below). I also recommend upgrading from one stable version to another, don't jump versions. I was upgrading from 19.4 to 20.2 and the when I tried to upgrade from 19.4 to 20.2 screenconnect threw a error related to Mono and did not work. Eventually, I figured out that it was necessary to upgrade to 19.6 first, then 20.2.
All the ScreenConnect files live in /opt/screenconnect. On my implementation these files live in /var/lib/docker/volumes/screenconnect_opt/_data. You need to be root to access this directory.
- Download the installation
- Copy to /var/lib/docker/volumes/screenconnect_opt/_data
- Move out of /opt/screenconnect
- Extract files from tar.gz file
- Run install.sh
Steps #3-5 need to be done from within the container. Use
docker exec -it sc bash
to get a container shell to do this.
External Accessibility Check
Once your data is migrated and your container upgraded to the latest version Click on Admin -> Status in the ScreenConnect application and check the External Accessibility Check. In my testing both the Web Server Test URL and Relay Test URL failed in version 19.4 and 19.6 and the Relay Test URL failed in version 20.2.
Don't worry about this while you are upgrading. Only once the upgrade process is complete should this be addressed (if needed).
Edit the web.config file in /var/lib/docker/volumes/screenconnect_opt/_data. Add the following to fix the Web Server Test URL:
<add key="WebServerAddressableUri" value="http://host.yourdomain.com:8040" />
And this to fix the Relay Test URL:
<add key="RelayAddressableUri" value="relay://host.yourdomain.com:8041" />
At this point all your data should be migrated, your screenconnect container upgraded to the latest version and any external accessibility issues resolved. Now all you need to do is update the image in the docker-compose.yml file to your latest image.
Pat yourself on the back. You did great.
Step 4: Traefik & Let's Encrypt
ScreenConnect has a back a$$wards way of implementing SSL. I could never figure it out. No matter, because it is possible to secure your screenconnect instance using Traefik and SSL.
What is Traefik?
Traefik is a modern HTTP reverse proxy and load balancer. Its tag line is, "Makes Networking Boring". I found this partly true. Traefik does make setting up reverse proxy easier than other softwares, but getting it to work was a challenge for me. It has great documentation which kind of sucks. It is great, because it lays out what you can do very well. It sucks, because it does not inform you on how to do it. There are multiple ways to configured Traefik: command-line, files and directly in a container.
Complaints aside, it is better than the alternatives.
Here is the docker-compose.yml file I used to create my Traefik container:
version: '3' services: traefik: container_name: traefik image: traefik:v2.4 restart: unless-stopped command: - "--providers.docker=true" # - "--api.insecure=true" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--certificatesResolvers.email@example.com" - "--certificatesResolvers.le.acme.storage=acme.json" - "--certificatesResolvers.le.acme.tlsChallenge=true" - "--certificatesResolvers.le.acme.httpChallenge=true" - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=web" networks: - proxy ports: - 80:80 - 443:443 - "8080:8080" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./data/acme.json:/acme.json
And here is the updated docker-compose.yml I used for my screenconnect container:
version: '3' services: screenconnect: container_name: sc image: wmc/sc:20.2 restart: unless-stopped volumes: - opt:/opt/screenconnect ports: - 8041:8041 expose: - 8040 labels: - traefik.enable=true - traefik.http.routers.sc.rule=Host(`host.yourdomain.com`) - traefik.http.services.sc.loadbalancer.server.port=8040 - traefik.http.routers.sc.entrypoints=websecure - traefik.http.routers.sc.tls=true - traefik.http.routers.sc.tls.certresolver=le networks: - proxy volumes: opt: networks: proxy: external: true
Let's go through the interesting bits line by line:
Traefik can work with several different container platforms this command says, "work with docker".
Tells Traefik to make its dashboard available. You can access it by going to http://host.yourdomain.com:8080. Traffic is not encrypted and there is no password. There are ways to implement these things and I did not do that. I have this commented out in my docker-compose.yml file. I basically turn it on when I need to setup a new service and turn it off when I do not need it.
- "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443"
Defines two entry points: web and websecure. Note that "web" and "websecure" can be whatever you want and are referred to in later configurations.
- "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
Defines http redirector for the "web" entry point. Says redirect to websecure and use HTTPS.
- "--certificatesResolvers.firstname.lastname@example.org" - "--certificatesResolvers.le.acme.storage=acme.json" - "--certificatesResolvers.le.acme.tlsChallenge=true" - "--certificatesResolvers.le.acme.httpChallenge=true" - "--certificatesResolvers.le.acme.httpChallenge.entryPoint=web"
Configures Let's Encrypt. "le" stands for Let's Encrypt, but this can be whatever you want it to be. For example, it could be "letsencrypt", "freeca" or whatever makes the most sense to you.
Now for the interesting bits in the screenconnect docker-compose.yml
ports: - 8041:8041 expose: - 8040
We keep ScreenConnect's relay port reachable by the Internets (this is already encrypted). We expose port 8040 internally on the Docker networks.
Enable traefik on this container.
Rule to match http traffic to host.yourdomain.com. Note: the "sc" can be whatever you wish.
This is how you tell traefik that you want to forward this traffik to port 8040 on the screenconnect container.
- traefik.http.routers.sc.entrypoints=websecure - traefik.http.routers.sc.tls=true - traefik.http.routers.sc.tls.certresolver=le
Defines the entry point, tells it to use TLS and to use Let's Encrypt as a certificate resolver.
Notice the use of "websecure" and "le" these were defined in the configuration of traefik in its docker-compose.yml file.
Last note: you will have to create the proxy network manually.
docker network create proxy
That is it. I think. I hope this saves you time. Let me know if you have any questions or comments.