Showing posts with label docker. Show all posts
Showing posts with label docker. Show all posts

04 January 2026

Portainer and Docker 29

Portainer was not able to access the local environment with the following error: "Failed loading environment The environment named local is unreachable". Then I found out that it was not compatible with Docker 29. Here are the steps that I used to downgrade Docker to a compatible version.


Downgrade Docker

  • Check the version installed
    • apt list --installed docker-ce
  • Check for available version
    • apt-cache policy docker-ce | head -n 30
  • Downgrade
    • DOCKER_VERSION="5:28.5.2-1~debian.12~bookworm"
    • apt-get install docker-ce-cli="$DOCKER_VERSION" docker-ce="$DOCKER_VERSION" docker-ce-rootless-extras="$DOCKER_VERSION"
  • Restart
    • shutdown -r now


Appendix

Sources


06 September 2025

Replacing HDHomeRun tuner

Since my HDHomeRun Connect died in a lightning storm possibly due to static build-up. I bought a replacement HDHomeRun Flex Duo as well as a lightning arrester/suppressor with a ground.

Here are the ones that I went with:

  • $109.99 HDHomeRun Flex Duo: Link
  • $17.95 Proxicast Coaxial Lightning Arrester/Suppressor: Link

I had considered the 4 tuner with ATSC 3.0 support, but with the threat of the channels becoming encrypted and the increased price made me go with the duo.


Device Setup

  • Add the lightning arrester/suppressor between the antenna and TV tuner and attach a ground wire
  • Plug in antenna, ethernet, and power into the HDHomeRun
  • Assign the HDHomeRun a static IP in your router
  • Reboot the HDHomeRun
  • Update the firmware: http://hdhomerun.local


Preparing Docker

setup.yml should be a copy of the mythtv docker-compose.yml, but update the image to dheaps/mythbackend:setup and add VNC_PASS=<super_secret_pass> to the environment section. Some key portions are:

  • network_mode: host
    • otherwise mythtv-setup will not be able to detect your HDHomeRun
  • hostname: <your_hostname>
    • otherwise mythtv-setup will use whatever generated hostname docker feeds it
  • environment variable VNC_PASS
    • the password to connect to VNC


Starting Docker

First and most importantly make sure your mythbackend is NOT running. I did this with:

  • docker compose down
  • docker ps

Next is starting the docker containers for mythtv-setup:

  • docker compose --file setup.yml up


Running Setup

Connect vnc to <ip_address>:5900. Then you will get prompted with several questions.

  • Add user to mythtv group? NO
  • Ignore the error and continue? NO
  • Would you like to start the mythtv backend? NO
  • Would you like to run mythfilldatabase? NO

After answering the questions,  I got dumped to the terninal.

On the command line run:

  • mythtv-setup.real

When done with setup exit with ESC and make sure to save changes if prompted. Then close vnc and press ctrl-C on the terninal running the docker container.


Appendix

Troubleshooting

  • mythweb: Unable to connect to 127.0.0.1:6543
    • in setup make sure that under General -> IP v4 address is set to the correct IP

Sources


10 June 2024

r730xd Server Part 3: Software

 I will be using Proxmox Helper Scripts (https://tteck.github.io/Proxmox/ or https://Helper-Scripts.com) to help configure the different LXCs and VMs that I want.


Disable nag screen

As I was tired of having to confirm that I didn't have a subscription, I ripped these commands from Proxmox VE Tools -> Proxmox VE Post Install (https://raw.githubusercontent.com/tteck/Proxmox/main/misc/post-pve-install.sh) and ran on the Shell command line

  • echo "DPkg::Post-Invoke { \"dpkg -V proxmox-widget-toolkit | grep -q '/proxmoxlib\.js$'; if [ \$? -eq 1 ]; then { echo 'Removing subscription nag from UI...'; sed -i '/.*data\.status.*{/{s/\!//;s/active/NoMoreNagging/}' /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js; }; fi\"; };" >/etc/apt/apt.conf.d/no-nag-script
  • apt --reinstall install proxmox-widget-toolkit


Setup Users

My philosophy was to put all actual users in the 1000s and system users in the 2000s
  • user1
    • useradd user1
    • usermod -g users user1
    • adduser user1 user1
    • id user1
  • user2
    • useradd user2
    • usermod -g users user2
    • adduser user2 user2
    • id user2
  • mythtv
    • groupadd -g 2001 mythtv
    • useradd -u 2001 -g 2001 mythtv
  • nginx
    • groupadd -g 2002 nginx
    • useradd -u 2002 -g 2002 nginx
  • edit /etc/subuid and add
    • root:1000:1000
    • root:2000:1000
  • edit /etc/subgid and add
    • root:100:1
    • root:1000:1000
    • root:2000:1000


Import ZFS

  • zpool import storage
  • I decided not to map the drive in Proxmox, but if you wanted to you would do that here
    • proxmox -> Datacenter -> Storage -> Add -> ZFS


Fix Directory/File Permissions

  • /storage/mythtv
    • cd /storage/mythtv
    • ls -al
    • find ./ -user <current owner> -print0 | xargs -0 chown -h mythtv
    • find ./ -group <current group> -print0 | xargs -0 chgrp -h mythtv
  • /storage/containers/mythtv
    • cd /storage/containers/mythtv
    • ls -al
    • find ./ -user <current owner> -print0 | xargs -0 chown -h mythtv
    • find ./ -group <current group> -print0 | xargs -0 chgrp -h mythtv
  • /storage/containers/webserver
    • cd /storage/containers
    • chown -R nginx webserver
    • chgrp -R nginx webserver


Create a Docker LXC

Now I needed a Docker LXC to run my webserver and MythTV

  • bash -c "$(wget -qLO - https://github.com/tteck/Proxmox/raw/main/ct/docker.sh)"
  • I then edited the config to set a static IP and change the hostname
    • docker -> Network -> net0 -> Edit
    • docker -> DNS -> Hostname -> Edit
  • Add users
    • nas
      • groupadd -g 2000 nas
      • useradd -u 2000 -g 2000 nas
    • mythtv
      • groupadd -g 2001 mythtv
      • useradd -u 2001 -g 2001 mythtv
    • nginx
      • groupadd -g 2002 nginx
      • useradd -u 2002 -g 2002 nginx


MythTV

  • Edit /storage/containers/mythtv/docker-compose.yml
    • Change the User Ids and Groups Ids to 2001
  • Test
    • docker compose up -d
  • If it looks like API port changed from 6544 to 6744, then you need to fix the IPs
  • Check pin and update backend ip
    • apt-get install default-mysql-client
    • mysql -p -h 127.0.0.1 -P 3306 mythconverg
      • select * from settings where value like '%pin%'
      • update settings set data = '192.168.1.31' where data = '192.168.1.11' ;
      • quit;
  • Restart MythTV and Test
    • docker compose down && docker compose up -d
  • This time I put a copy of docker-mythtv.service in /storage/container/mythtv so I can easily copy it to /etc/systemd/system/ in the future
    • make sure to change `docker-compose` to `docker compose`
  • docker compose down
  • systemctl enable docker-mythtv
  • systemctl start docker-mythtv


Webserver

  • Test
    • docker compose up -d
    • docker compose down
  • This time I put a copy of docker-webserver.service, certbot.service, and certbot.timer in /storage/container/webserver/systemd so I can easily copy it to /etc/systemd/system/ in the future
    • make sure to change `docker-compose` to `docker compose`
  • cp certbot.service certbot.timer docker-webserver.server /etc/systemd/system/
  • systemctl enable docker-webserver
  • systemctl start docker-webserver
  • systemctl enable certbot.timer


Create a Debian LXC for File Sharing

  • following:
  • bash -c "$(wget -qLO - https://github.com/tteck/Proxmox/raw/main/ct/debian.sh)"
  • Add users/groups
    • <container> -> Console
    • user1
      • useradd user1
      • usermod -g users user1
      • adduser user1 user1
      • id user2
    • user2
      • useradd user2
      • usermod -g users user2
      • adduser user2 user2
      • id user2
    • nas
      • groupadd -g 2000 nas
      • useradd nas -u 2000 -g 2000 -m -s /bin/bash
      • adduser nas sudo
      • passwd nas
  • shutdown the container
  • Set a static IP and change the hostname
    • <container> -> Network -> net0 -> Edit
    • <container> -> DNS -> Hostname -> Edit
  • Add more compute/memory (2 cores/1024MB)
    • <container> -> Resources -> Cores -> Edit
    • <container> -> Resources -> Memory -> Edit
  • Add our storage
    • edit /etc/pve/lxc/<container id>.conf
    • add a line for each of your datasets like the below examples:
      • mp0: /storage/folder1/dataset1,mp=/storage/folder1/dataset1
      • mp1: /storage/folder1/dataset2,mp=/storage/folder1/dataset2
      • mp2: /storage/dataset3,mp=/storage/dataset3
  • Map the users
    • Notes
      • /etc/subuid and /etc/subgid need to specify the user starting the lxc container (root)
      • /etc/pve/lxc/<container id>.conf needs to map all ids and not just the ones you want to remap
    • edit /etc/pve/lxc/<container id>.conf add these lines
      • lxc.idmap: u 0 100000 1000
      • lxc.idmap: u 1000 1000 1000
      • lxc.idmap: u 2000 102000 63535
      • lxc.idmap: g 0 100000 100
      • lxc.idmap: g 100 100 1
      • lxc.idmap: g 101 100101 899
      • lxc.idmap: g 1000 1000 1000
      • lxc.idmap: g 2000 102000 63535
  • Change the drive permissions
    • I couldn't get the container to boot with trying to remap root so don't do this step
    • cd /rpool/data/subvol-<container id>-disk-0
    • find ./ -user 100000 -print0 | xargs -0 chown -h 2000
    • find ./ -group 100000 -print0 | xargs -0 chgrp -h 2000
  • Start the container
  • Install cockpit
    • apt install cockpit --no-install-recommends
    • wget https://github.com/45Drives/cockpit-file-sharing/releases/download/v3.3.7/cockpit-file-sharing_3.3.7-1focal_all.deb
    •  wget https://github.com/45Drives/cockpit-navigator/releases/download/v0.5.10/cockpit-navigator_0.5.10-1focal_all.deb
    • wget https://github.com/45Drives/cockpit-identities/releases/download/v0.1.12/cockpit-identities_0.1.12-1focal_all.deb
    • apt install ./*.deb
    • rm *.deb
  • Configure cockpit
    • https://192.168.X.X:9090
    • use nas to login
    • enable administrative access
    • Identities
      • Set a Samba password for each of the users
    • File Sharing
      • Click "Fix Now"
      • Global Settings
        • Toggle Global MacOS Shares
        • Add `allow insecure wide links = yes` to Advanced
        • Apply
      • Add your shares
      • I used the following to ensure all users can access others files in advanced
        • create mask = 0664
        • force create mode = 0664
        • directory mask = 0775
        • force directory mode = 0775
      • I used the following to be able to follow symlinks
        • follow symlinks = yes
        • wide links = yes


Create a Ubuntu VM for VNC and Handbrake

I have read that x264 sees reduced performance with over 6 threads, so I gave the VM 12 virtual cores since I want to be able to process 2 discs at a time.

  • bash -c "$(wget -qLO - https://github.com/tteck/Proxmox/raw/main/vm/ubuntu2404-vm.sh)"
  • Edit Cloud-Init
    • ubuntu -> Cloud-Init
    • set user/pass
    • set static IP
  • Disable start at boot
    • ubuntu -> Options -> Start at boot -> Edit
    • uncheck and the hit OK
  • Change resources
    • 12 Cores
    • 24 GB of memory (2 GB per core)
    • added 16 GB of storage
  • Console
    • start VM
    • login
  • Enable ssh with password
    • /etc/ssh/sshd_config.d/10_users.conf
      • Match User username
        • PasswordAuthentication yes
    • sudo systemctl restart ssh
  • VNC server
    • sudo apt install tigervnc-standalone-server
  • A window manager and terminal to use inside VNC
    • sudo apt install xfce4 xfce4-terminal
  • Keep proxmox console as text
    • sudo systemctl set-default multi-user.target
  • Start VNC
    • tigervncserver :1 -geometry 1600x900 -depth 24 -localhost no -SecurityTypes VncAuth,TLSVnc -xstartup /usr/bin/startxfce4
  • Add handbrake
    • sudo apt install handbrake libdvd-pkg
    • sudo dpkg-reconfigure libdvd-pkg
  • Set timezone (Added 2024-0620)
    • timedatectl list-timezones
    • sudo timedatectl set-timezone America/New_York
  • Setup samba shares
    • sudo apt install cifs-utils
    • create a file to save samba credentials in (eg smbcredentials)
      • username=username
      • password=password
    • mount the shares
      • sudo mount -t cifs //192.168.1.XX/nas /storage/encrypted/nas -o credentials=/home/<username>/smbcredentials,uid=<username>,gid=users
  • Add qemu agent (added 2024-07-29)
    • sudo apt install qemu-guest-agent
    • sudo systemctl start qemu-guest-agent


Appendix

Error
  •  Failed to run lxc.hook.pre-start for container
    • are your zfs pools mounted?
  • make sure cifs-utils are installed
    • sudo apt install cifs-utils
  • samba files are all owned by root
    • make sure to add the uid and gid to the mount command
Sources

26 May 2024

Dockerize MythTV

To prepare for my new server, I needed to migrate the rest of MythTV including its MySQL database to Docker containers.


Migrate MythTV Backend and MySQL to Docker

  • Edit docker-compose-webserver.service to remove references to mysql.service
    • This step is only necessary if you followed my other guide that dockerized mythweb
    • sudo systemctl daemon-reload
  • Stop and Disable MythTV Backend
    • sudo systemctl stop mythtv-backend
    • sudo systemctl disable mythtv-backend
  • Create directories and set permissions
    • sudo mkdir -p /storage/containers/mythtv/mysql/lib
    • sudo chown -R mysql /storage/containers/mythtv/mysql
    • sudo chgrp -R mysql /storage/containers/mythtv/mysql
  • Dump the current mythconverg database to a file and update the hostname
    • sudo su
    • mysqldump -p<existing root password> mythconverg > mythconverg.mysqldump
    • sed "s/'<old hostname>'/'mythtv-backend'/g" mythconverg.mysqldump > mythconverg.mysqldump.host
    • exit
  • Stop and disable current MySQL instance
    • sudo systemctl stop mysql
    • sudo systemctl disable mysql
  • Identify the needed UIDs and GIDs
    • id mysql && id mythtv
  • Create the docker-compose.yml with just a MySQL section
# Begin docker-compose.yml
version: '3.5'
services:
    mythtv-mysql:
        container_name: 'mythtv-mysql'
        hostname: 'mythtv-mysql'
        user: '<mysql uid>:<mysql gid>'
        image: mysql:latest
        restart: always
        ports:
            - '3306:3306'
        environment:
            - MYSQL_ROOT_PASSWORD=RootSuperSecretPassword
            - MYSQL_USER=mythtv
            - MYSQL_PASSWORD=YourSuperSecretPassword
        volumes:
            - ./mysql/lib:/var/lib/mysql
# End docker-compose.yml
  • Restore the database:
    • sudo docker-compose up --build
    • sudo mysql -p -h 127.0.0.1 -P 3306
      • create database mythconverg ;
      • connect mythconverg ;
      • source mythconverg.mysqldump.host ;
      • grant all privileges on mythconverg to 'mythtv'@'%' ;
      • grant all privileges on mythconverg.* to 'mythtv'@'%' ;
      • quit ;
    • Use Ctrl-C to stop the container
  • Add the backend to docker-compose.yml
# Begin docker-compose.yml
version: '3.5'
services:
    mythtv-mysql:
        container_name: 'mythtv-mysql'
        hostname: 'mythtv-mysql'
        user: '<mysql uid>:<mysql gid>'
        image: mysql:latest
        restart: always
        ports:
            - '3306:3306'
        environment:
            - MYSQL_ROOT_PASSWORD=RootSuperSecretPassword
            - MYSQL_USER=mythtv
            - MYSQL_PASSWORD=YourSuperSecretPassword
        volumes:
            - ./mysql/lib:/var/lib/mysql
    mythtv-backend:
        container_name: 'mythtv-backend'
        hostname: 'mythtv-backend'
        user: '<mythtv uid>:<mythtv gid>'
        image: dheaps/mythbackend:latest
        restart: always
        depends_on:
            - mythtv-mysql
        network_mode: host
        # entrypoint: 'echo backend disabled'
        environment:
            - USER_ID=<mythtv uid>
            - GROUP_ID=<mythtv gid>
            - DATABASE_HOST=host.docker.internal
            - DATABASE_PORT=3306
            - DATABASE_NAME=mythconverg
            - DATABASE_USER=mythtv
            - DATABASE_PWD=YourSuperSecretPassword
            - TZ=America/New_York
        volumes:
            - /storage/mythtv:/var/lib/mythtv
        extra_hosts:
            - host.docker.internal:host-gateway
# End docker-compose.yml
  • Test it out
    • docker-compose up --build
    • Ctrl-C to stop it once you confirm it works
  • Add it to systemd: /etc/systemd/system/docker-mythtv.service
# Begin docker-mythtv.service
[Unit]
Description=Docker Compose MythTV Service
Requires=docker.service
After=docker.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/storage/containers/mythtv
ExecStart=/usr/bin/docker-compose up --build -d
ExecStop=/usr/bin/docker-compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
# End docker-mythtv.service

  • Enable and start the service
    • sudo systemctl enable docker-mythtv
    • sudo systemctl start docker-mythtv
  • I also had to update docker-compose.yml for mythweb to use DATABASE_HOST=host.docker.internal and add the extra_hosts


Appendix

Errors

  • MythCoreContext::CheckSubnet(): Repeat denied connection from ip address
    • Connect to the MySQL database
      • sudo mysql -p -h 127.0.0.1 -P 3306
    • Run one of the following:
      • update settings set data = 1 where value = 'AllowConnFromAll' ;
      • OR
      • insert into settings(value, data, hostname)
      • values('AllowConnFromAll', '1', 'mythtv-backend')
      • ;
  • only one instance of "host" network is allowed
    • ensure that you are using either external host network or network_mode

Sources



23 March 2024

Dockerize my LAMP webserver

As my main SSD is running low on estimated life remaining, I am attempting to containerize my projects so that they can be easily moved. The primary one is my Linux Apache MySQL PHP (LAMP) webserver that I use for lists and MythWeb.

For this project I will be using docker compose to put a nginx reverse proxy in front of Apache so that it can direct the direct the traffic, handle ssl encrpytion, and authentication. We will first be setting it up on some dev ports (9080/9443) so that we can test before replacing the existing servers.


Move docker data onto zfs

  • This will keep the images and logs on zfs instead of my root drive
  • It will also require any image to be downloaded again and rebuilt
  • Stop dockerd
    • sudo systemctl stop docker
    • sudo systemctl stop docker.socket
  • Move the docker data
    • sudo mkdir /storage/containers/dockerd
    • sudo rsync -avh --progress /var/lib/docker/ /storage/containers/dockerd
    • sudo mv /var/lib/docker /var/lib/docker.old
  • edit /etc/docker/daemon.json
{
    "data-root": "/storage/containers/dockerd",
    "storage-driver": "zfs"
}
  • Restart dockerd
    • sudo systemctl start docker.socket
    • sudo systemctl start docker


General Setup

  • Create a place to store all the files
    • this should be on your zfs dataset
    • sudo mkdir -p /storage/containers/webserver 
  • We will use this as the root directory for all of the below configs
  • Create the needed subdirs
    • cd /storage/containers/webserver
    • sudo mkdir -p letsencrypt/etc letsencrypt/data letsencrypt/logs
    • sudo mkdir -p nginx/www/html
    • sudo mkdir -p lists/build lists/mysql lists/www/html/lists


First setup nginx

  • Setup valid users for authentication
    • sudo htpasswd -c nginx/www/htpasswd username
  • Copy the existing letsencrypt certs
    • sudo mkdir -p letsencrypt/etc/live/this.example.com
    • sudo cp /etc/letsencrypt/live/this.example.com/* letsencrypt/etc/live/this.example.com/
  • create a docker-compose.yml with the following contents:
# Begin docker-compose.yml
version: '3.4'

services:
    nginx:
        container-name: 'nginx-proxy'
        hostname: 'nginx-proxy'
        image: nginx:latest
        ports:
            - "9080:80"
            - "9443:443"
        volume:
            - ./prod.conf:/etc/nginx/conf.d/default.conf
            - ./nginx/www:/www
            - ./letsencrypt/etc:/etc/letsencrypt
            - ./letsencrypt/data:/data/letsencrypt
# End docker-compose.yml
  • create a prod.conf with the following contents:
# Begin prod.conf
server {
    listen      80;
    listen [::]:80;
    server_name this.example.com;

    location / {
        rewrite ^ https://$host:9443$request_uri? permanent;
    }

    # for cerbot challenge
    location /.well-known/acme-challenge {
        allow all;
        root /data/letsencrypt;
    }
}

server {
    listen      443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name this.example.com;

    ssl_certificate     /etc/letsencrypt/live/this.example.com/full chain.pem;
    ssl_certificate_key /etc/letsencrypt/live/this.example.com/privkey.pem;

    auth_basic "Your Server Message";
    auth_basic_user_file /www/htpasswd;

    location / {
        root /www/html;
    }
}
# End prod.conf

  • create a nginx/www/html/index.html that will link to our actual contents, here is my example:
<html> 
<body>
    <p>
        <a href="lists/">Lists</a>
    </p>
    <p>
        <a href="mythweb/">MythWeb</a>
    </p>
</body>
</html>

  • Now test the server
    • sudo docker-compose up --build
  • Visit your site in a browser
  • Ctrl+C to stop the server


Setup MythWeb

  • Add the mythweb section to docker-compose.yml so that it looks like:
# Begin docker-compose.yml
version: '3.4'

services:
    nginx:
        container-name: 'nginx-proxy'
        hostname: 'nginx-proxy'
        image: nginx:latest
        restart: always
        ports:
            - "9080:80"
            - "9443:443"
        volume:
            - ./nginx/prod.conf:/etc/nginx/conf.d/default.conf
            - ./nginx/www:/www
            - ./letsencrypt/etc:/etc/letsencrypt
            - ./letsencrypt/data:/data/letsencrypt
    mythweb:
        container-name: 'myth-http'
        hostname: 'myth-http'
        image: dheaps/mythbackend:mythweb
        restart: always
        ports:
            - "7080:80"
        environment:
            - DATABASE_HOST=localhost
            - DATABASE_NAME=mythconverg
            - DATABASE_USER=mythtv
            - DATABASE_PASSWORD=YourSuperSecretPassword
            - TZ=America/New_York
        volumes:
            # This will have mysql connect over sockets instead ports
            /var/run/mysqld/mysql.sock:/var/run/mysqld/mysql.sock
# End docker-compose.yml

  • Add the mythweb sections to prod.conf so that it looks like this:
# Begin nginx/prod.conf
server {
    listen      80;
    listen [::]:80;
    server_name this.example.com;

    location / {
        rewrite ^ https://$host:9443$request_uri? permanent;
    }

    # for cerbot challenge
    location /.well-known/acme-challenge {
        allow all;
        root /data/letsencrypt;
    }
}

server {
    listen      443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name this.example.com;

    ssl_certificate     /etc/letsencrypt/live/this.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/this.example.com/privkey.pem;

    auth_basic "Your Server Message";
    auth_basic_user_file /www/htpasswd;

    location / {
        root /www/html;
    }

    location /mythweb {
        # Use this to preserve port number
        return 301 $scheme://$http_host/mythweb/;
    }
    location /mythweb/ {
        proxy_pass http://myth-http:80/mythweb/;
        proxy_buffering off;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        # using $http_host so that the links will include port
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Forwarded-Port $server_port;
    }
}
# End nginx/prod.conf
  • Now test the server
    • sudo docker-compose up --build
  • Visit your site in a browser
  • Ctrl+C to stop the server


Setup MySQL and Apache

  • Note: we are using MYSQL_HOST environment variable so that the PHP can easily switch what MySQL instance to connect to
  • Create a directory to hold related files
    • mkdir -p lists/build
    • mkdir -p lists/mysql
  • Copy your HTML/PHP files into lists/www/html/
    • sudo mkdir -p lists/www/html/lists
    • sudo cp /var/www/html/lists/* lists/www/html/lists/
  • Add the MySQL and Apache sections to docker-compose.yml
# Begin docker-compose.yml
version: '3.4'

services:
    nginx:
        container-name: 'nginx-proxy'
        hostname: 'nginx-proxy'
        image: nginx:latest
        restart: always
        ports:
            - "9080:80"
            - "9443:443"
        volume:
            - ./nginx/prod.conf:/etc/nginx/conf.d/default.conf
            - ./nginx/www:/www
            - ./letsencrypt/etc:/etc/letsencrypt
            - ./letsencrypt/data:/data/letsencrypt
    mythweb:
        container-name: 'myth-http'
        hostname: 'myth-http'
        image: dheaps/mythbackend:mythweb
        restart: always
        ports:
            - "7080:80"
        environment:
            - DATABASE_HOST=localhost
            - DATABASE_NAME=mythconverg
            - DATABASE_USER=mythtv
            - DATABASE_PASSWORD=YourSuperSecretPassword
            - TZ=America/New_York
        volumes:
            # This will have mysql connect over sockets instead ports
            - /var/run/mysqld/mysql.sock:/var/run/mysqld/mysql.sock
    lists-www:
        container-name: 'lists-www'
        hostname: 'lists-www'
        build: './lists/build'
        restart: always
        environment:
            - MYSQL_HOST=lists-mysql
        volumes:
            - ./lists/http.conf:/etc/apache2/httpd.conf
            - ./lists/www:/var/www
            # This is just where I keep my lists and isn't required
            - /home/user/Documents/lists:/home/user/Documents/lists
    lists-mysql:
        container-name: 'lists-mysql'
        hostname: 'lists-mysql'
        image: mysql:latest
        restart: always
        ports:
            - "4306:3306"
        environment:
            - MYSQL_ROOT_PASSWORD=AnotherSuperSecretPassword
        volumes:
            - ./lists/mysql:/var/lib/mysql
# End docker-compose.yml

  • Create lists/build/Dockerfile
# Begin lists/build/Dockerfile
FROM php:apache
RUN  apt-get update && docker-php-ext-install mysqli pdo pdo_mysql
# End lists/build/Dockerfile
  • Create lists/http.conf
# Begin lists/http.conf
<VirtualHost>
    ServerName this.example.com
    ServerAdmin admin@this.example.com
    DocumentRoot /var/www/html
</VirtualHost>
# End lists/http.conf
  • Now test the server
    • sudo docker-compose up --build
  • While the test server is up load data into your MySQL instance
    • Note: Don't use localhost or MySQL will ignore the port and use sockets
    • sudo mysql -p -h 127.0.0.1 --port=4306
  • Visit your site in a browser
  • Ctrl+C to stop the server


Move to production

  • Edit the configs
    • in docker-compose.yml replace 9080 with 80 and 9443 with 443
    • in nginx/prod.conf replace 9443 with 443
  • Stop the normal apache and keep it from starting at boot
    • sudo systemctl stop apache2
    • sudo systemctl disable apache2
  • Stop the normal certbot
    • sudo systemctl disable certbot.timer
  • Have docker compose start at startup
    • /etc/systemd/system/docker-compose-webserver.service
# Begin /etc/systemd/system/docker-compose-webserver.service
# Only include mysql.service if dependent on it for mythweb
[Unit]
Description=Docker Compose Webserver Service
Requires=docker.service
Wants=mysql.service
After=docker.service mysql.service

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=/storage/containers/webserver
ExecStart=/usr/bin/docker-compose up --build -d
ExecStop=/usr/bin/docker-compose down
TimeoutStartSec=0

[Install]
WantedBy=multi-user.target
# End /etc/systemd/system/docker-compose-webserver.service
    • Load and start docker-compose-webserver
      • sudo systemctl daemon-reload
      • sudo systemctl enable docker-compose-webserver
      • sudo systemctl start docker-compose-webserver


Setup the certificate renewal

  • Unfortunately, the site needs to be live before we can test/setup certificate renewal, so make sure you did the steps above
  • Remove the certs that we copied in, as certbot needs a blank folder or it will add -0001 to our hostname directory
    • sudo rm -rf ./letsencrypt/etc/*
  • Use staging (the test environment) to check the commands
sudo docker run -it --rm \
    -v ./letsencrypt/data:/data/letsencrypt \
    -v ./letsencrypt/etc:/etc/letsencrypt \
    -v ./letsencrypt/logs:/var/logs/letsencrypt \
    certbot/certbot \
    certonly --webroot \
    --register-unsafely-without-email --agree-tos \
    --webroot-path=/data/letsencrypt \
    --staging \
    -d this.example.com
  • If the above worked then we can try a live renewal (which is rate limited)
sudo docker run -it --rm \
    -v ./letsencrypt/data:/data/letsencrypt \
    -v ./letsencrypt/etc:/etc/letsencrypt \
    -v ./letsencrypt/logs:/var/logs/letsencrypt \
    certbot/certbot \
    certonly --webroot \
    --email youremail@domain.com --agree-tos --no-eff-email \
    --webroot-path=/data/letsencrypt \
    -d this.example.com
  • If the above worked then we schedule automatic renewal
  • Create if does not exist /lib/systemd/system/cerbot.service
# Begin certbot.timer
[Unit]
Description=Run certbot twice daily

[Timer]
OnCalendar=*-*-* 00,12:00:00
RandomizedDelaySec=43200
Persistent=true

[Install]
WantedBy=timers.target
# End certbot.timer
  • Edit/create /lib/systemd/system/cerbot.service
# Begin certbot.service
[Unit]
Description=Certbot
Documentation=file:///usr/share/doc/python-certbot-doc/html/index.html
Documentation=letsencrypt.readthedocs.io/en/latest

[Service]
Type=oneshot
# ExecStart=/usr/bin/certbot -q renew
WorkingDirectory=/storage/containers/webserver
ExecStart=docker run --rm \
    -v ./letsencrypt/data:/data/letsencrypt \
    -v ./letsencrypt/etc:/etc/letsencrypt \
    -v ./letsencrypt/logs:/var/logs/letsencrypt \
    certbot/certbot \
    renew --quiet --webroot \
    --email youremail@domain.com --agree-tos --no-eff-email \
    --webroot-path=/data/letsencrypt
ExecStartPost=docker exec nginx-proxy nginx -s reload
PrivateTmp=true
# End certbot.service
  • Enable the service
    • sudo systemctl daemon-reload
    • sudo systemctl enable certbot.timer

Update 2024-05-09: You need to restart nginx after getting a new certificate. I haven't figured out the best way to automate this.

Update 2024-05-10: Added the ExecStartPost in certbot.service to have nginx reload after renewing

Update 2024-05-26: Added `restart: always` so that the services will restore after they fail or docker fails.


Debug

  • If you see errors like `Cannot create container for service`
    • view all containers:
      • docker ps -a
    • you can remove the offending container with:
      • docker rm <container-name>


Next Steps

  • Put mythbackend and its MySQL instance in docker


Appendix

Sources