In this detailed article, we will build a Docker lab for convenient management of Docker Compose projects through a graphical interface. And yes, only Open Source under the hood 🐧.
Preface

I deployed such a lab on my home Linux PC and now conveniently manage my docker compose services running not only on it, but also on remote servers. But first things first 📝.
🖐️Hey!
Subscribe to our Telegram channel @r4ven_me📱, so you don’t miss new posts on the website 😉. If you have questions or just want to chat about the topic, feel free to join the Raven chat at @r4ven_me_chat🧐.
Our future lab will be built around such a wonderful Open Source product as Komodo.
Below is a brief overview of the lab components.
- Komodo (as a Docker orchestrator) - intended for automating deployment, scaling, and management of containerized applications through a convenient control panel. It can manage Docker services on remote machines using agents.
- Nginx Proxy Manager - a convenient tool with a web interface that simplifies configuring Nginx as a reverse proxy and managing SSL certificates (for example, Let’s Encrypt or self-signed ones).
- Technitium - a functional DNS server, also with a web interface, that allows flexible management of domain zones and name resolution, ad blocking, and more. It can implement resolving over TCP, UDP, DoT, DoH, and so on.
- And many, many different compose projects 😉.
A bit more about Komodo under the spoiler
- Connecting an unlimited number of servers (Core + Periphery architecture - central Core and lightweight Periphery agents on nodes);
- Resource monitoring: CPU, memory, disk with alerts;
- Access to server shell sessions through the browser;
- Creating, starting, stopping, restarting, and redeploying containers;
- Viewing status and logs in real time;
- Exec into a container (connecting to a shell inside a container directly from the UI);
- Deploying stacks from the UI, from the host, or from a Git repository;
- Automatic redeploy on push to Git (through a webhook);
- Support for multiple compose files (docker compose -f … -f …);
- Passing environment variables;
- Importing existing compose projects;
- Building images from a Dockerfile directly in the UI;
- Automatic build from a Git repository (with a webhook);
- Image versioning support;
- Procedures and Actions - multi-step automated workflows;
- Resource Sync - declarative management of the entire configuration through TOML files in Git;
- Podman support (as an alternative to Docker);
- Fully self-hosted, open-source (GPLv3).
Lab diagram

There is a lot of material ahead, so fewer words and more action, penguins 🐧🐧🐧.
📝 All actions from this article were performed in the Linux Mint (Ubuntu) distribution environment, Cinnamon edition, and as a regular user with sudo privileges.
In all other Linux distributions, everything will look more or less similar.
Preparation
Installing Docker

Since we are building a lab for convenient management of Docker resources, install Docker Engine and add our user to the docker group.
sudo curl https://get.docker.com/ | bash
sudo gpasswd -a $USER docker
newgrp dockerAfter successful installation and adding the user to the Docker group, try running the command:
docker run hello-worldIf successful, we will see this:

Installing helper utilities
Now install utilities from the standard repositories that we will need while configuring our lab:
sudo apt update
sudo apt install -y libnss3-tools dnsutils acl mkcert pwgenlibnss3-tools- utilities for managing certificates and the NSS store;acl- tools for working with extended Access Control Lists in file systems;mkcert- a simple tool for creating locally trusted TLS certificates;pwgen- a random password generator.
Creating the working directory and setting permissions
Create a workdir and restrict access to it:
sudo mkdir -vp /opt/komodo/
sudo chmod -v 700 /opt/komodo
Set full permissions on the workdir and future files for the current user:
sudo setfacl -R -m m::rwx /opt/komodo
sudo setfacl -R -m d:m::rwx /opt/komodo
sudo setfacl -R -m u:ivan:rwX /opt/komodo
sudo setfacl -R -m d:u:ivan:rwX /opt/komodo
getfacl /opt/komodo
Generating certificates
Since I am showing an example of configuring a Docker lab on a desktop Linux PC, I will create a self-signed “certificate authority (RootCA)” and certificates for domains.
For this, use the previously installed mkcert utility:
mkcert -install
mkcert -CAROOTThe first command creates a local certificate authority and adds its root certificate to the trusted certificates on our system and to the stores of installed browsers, for example Firefox and Chromium:

Now create a directory for certificates and two certificates themselves:
- For the main local domain
home.lan; - And a wildcard certificate for subdomains
*.home.lan.
mkdir -vp /opt/komodo/periphery/stacks/nginx/certs
cd /opt/komodo/periphery/stacks/nginx/certs
mkcert -cert-file ./home.lan.crt -key-file ./home.lan.key "home.lan" "localhost" "127.0.0.1"
mkcert -cert-file ./_wildcard.home.lan.crt -key-file ./_wildcard.home.lan.key "*.home.lan"☝️ This exact path matters to us: /opt/komodo/periphery/stacks/nginx/certs.

(Optional) Adding the mkcert rootCA to trusted certificates on another host
If you plan to access your local host over HTTPS from other hosts, for example through a secure private network, simply copy the root certificate (not the key!) created by mkcert to the trusted certificates on another host and update the list.
Example for Debian:
scp ~/.local/share/mkcert/rootCA.pem user@target_host:/tmp/
ssh -t user@target_host sudo cp /tmp/rootCA.pem /usr/local/share/ca-certificates/mkcert-rootCA.crt
ssh -t user@target_host sudo update-ca-certificates
ssh user@target_host trust list | grep mkcertAdding hosts to /etc/hosts
Now we need to add 3 records to the local /etc/hosts file:
cat << EOF | sudo tee -a /etc/hosts
127.0.0.1 home.lan # Nginx proxy
127.0.0.1 komodo.home.lan # Docker manager
127.0.0.1 technitium.home.lan # DNS
EOFCreating the Docker network
For our installation, we will create a separate Docker network: komodo_net:
docker network create --opt com.docker.network.bridge.name=br-komodo-net --opt com.docker.network.enable_ipv6=false --driver bridge --subnet 10.51.51.0/24 --gateway 10.51.51.1 komodo_netPreparation is finished😌.
Configuring the proxy server - Nginx Proxy Manager
Proceed to configuring our proxy, which will manage certificates and HTTP and TCP proxying.
Running nginx-proxy-manager in Docker

Go to the nginx working directory and create the docker services file:
cd /opt/komodo/periphery/stacks/nginx/
vim ./compose.yamlFill it with the following content:
---
# https://nginxproxymanager.com/setup/
networks:
komodo_net:
external: true
services:
nginx:
image: jc21/nginx-proxy-manager:2.14.0
container_name: nginx
restart: unless-stopped
stop_grace_period: 30s
cpus: 2
mem_limit: 2G2.14.0
environment:
TZ: Europe/Moscow
DB_SQLITE_FILE: /data/database.sqlite
hostname: nginx
volumes:
- ./data/:/data/
- ./certs/letsencrypt:/etc/letsencrypt/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- 127.0.0.1:80:80 # HTTP
- 127.0.0.1:81:81 # Web UI
- 127.0.0.1:443:443 # HTTPS
# - 127.0.0.1:53:53 # DNS
networks:
komodo_net:
ipv4_address: 10.51.51.50
aliases:
- home.lan
- nginx-proxy-manager
- nginx-proxy
- nginx📝 Adjust the container resource limits for your needs. Parameters: cpus:, mem_limit:.
Start our first service and watch the output:
docker compose up -d && docker logs -f nginx
After a successful start, open http://localhost:81 in a web browser.
We will see a welcome window and a prompt to specify the administrator name/email and set a password for it:

💡 Tip
You can generate a strong password using the pwgen utility installed during preparation:
pwgen -s 32 1
Adding certificates to Nginx
At this step, we will configure proxy access to the Web GUI panels of our services (including nginx-proxy-manager) over HTTPS.
After logging in to the proxy admin panel, enable the dark theme 😉, change the interface language to Russian, and go to the certificate management section:

Click “Add Certificate” –> “Custom Certificate”. Specify:
- name -
home.lan; - key -
/opt/komodo/periphery/stacks/nginx/certs/home.lan.key; - certificate -
/opt/komodo/periphery/stacks/nginx/certs/home.lan.crt.

📝 Of course, you can also configure automatic certificate issuance and renewal from Let’s Encrypt if you are creating the lab on a machine with a public IP address.
Upload the wildcard certificate in the same way. As a result, you should have two certificates added:

Creating proxy hosts
Now go to “Hosts” –> “Proxy Hosts” and click “Add Proxy Host”:

In the “Domain Names” item, specify home.lan and press Enter. Next, in the “Forward Hostname / IP” field, specify the network alias of our proxy, nginx; in the “Forward Port” field, specify the port on which the proxy admin panel runs, 81. After filling this in, go to the “SSL” tab:

Here select the home.lan certificate, enable forced SSL and HTTP/2 support. Then click “Save”:

Add two more proxy hosts in the same way.
Komodo:
- Domain -
komodo.home.lan - Host -
komodo-core - Port -
9120 - Enable - Websockets Support
- SSL certificate -
_wildcard.home.lan - Enable - Force SSL
- Enable - HTTP/2 Support
Technitium:
- Domain -
technitium.home.lan - Host -
technitium - Port -
5380 - Enable - Websockets Support
- SSL certificate -
_wildcard.home.lan - Enable - Force SSL
- Enable - HTTP/2 Support
As a result, we will have three proxy hosts:

Check proxying by clicking the home.lan record in the “SOURCE” column. A new login page for the nginx panel will open, but now with a valid HTTPS connection:


No complaints about certificates or insecure connection😌.
Creating a stream for DNS queries
Now we will create one stream for proxying DNS queries to the technitium DNS server.
Go to “Hosts” –> “Streams” and click “Add Stream”:

Specify:
- Incoming Port -
53 - Forward Host -
technitium - Forward Port -
53 - TCP and UDP protocols
And click “Save”:

There will be this record:

Configuring the Docker manager - Komodo
Move on to the Docker resource manager - Komodo.
Running Komodo in Docker
Go to the working directory and create a compose file:
cd /opt/komodo
vim ./compose.yamlFill it in:
---
# https://github.com/moghtech/komodo/blob/main/compose/mongo.compose.yaml
networks:
komodo_net:
external: true
services:
komodo_db:
image: mongo:8.2.7-rc0
container_name: komodo-db
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
stop_grace_period: 1m
cpus: 2
mem_limit: 2G
hostname: komodo-db
command: --quiet --wiredTigerCacheSizeGB 0.25
volumes:
- ./db/data/:/data/db/
- ./db/config/:/data/configdb/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
environment:
TZ: Europe/Moscow
MONGO_INITDB_ROOT_USERNAME: ${KOMODO_DB_USERNAME}
MONGO_INITDB_ROOT_PASSWORD: ${KOMODO_DB_PASSWORD}
expose:
- 27017
networks:
komodo_net:
ipv4_address: 10.51.51.51
aliases:
- komodo-db.home.lan
- komodo-db
komodo_core:
image: ghcr.io/moghtech/komodo-core:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
depends_on:
- komodo_db
container_name: komodo-core
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
restart: unless-stopped
stop_grace_period: 1m
cpus: 2
mem_limit: 2G
hostname: komodo-core
env_file: ./.env
environment:
TZ: Europe/Moscow
KOMODO_DATABASE_ADDRESS: komodo-db:27017
KOMODO_DATABASE_USERNAME: ${KOMODO_DB_USERNAME}
KOMODO_DATABASE_PASSWORD: ${KOMODO_DB_PASSWORD}
volumes:
- ./core/backups/:/backups/
- ./core/syncs/:/syncs/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
expose:
- 9120
networks:
komodo_net:
ipv4_address: 10.51.51.52
aliases:
- komodo-core.home.lan
- komodo.home.lan
- komodo-core
- komodo
komodo_periphery:
image: ghcr.io/moghtech/komodo-periphery:${COMPOSE_KOMODO_IMAGE_TAG:-latest}
labels:
komodo.skip: # Prevent Komodo from stopping with StopAllContainers
container_name: komodo-periphery
stop_grace_period: 1m
cpus: 2
mem_limit: 2G
hostname: komodo-periphery
restart: unless-stopped
env_file: ./.env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /proc:/proc
## Specify the Periphery agent root directory.
## Must be the same inside and outside the container,
## or docker will get confused.
## Default: /etc/komodo.
- ${PERIPHERY_ROOT_DIRECTORY:-/opt/komodo}:${PERIPHERY_ROOT_DIRECTORY:-/opt/komodo}
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
expose:
- 8120
networks:
komodo_net:
ipv4_address: 10.51.51.53
aliases:
- komodo-periphery.home.lan
- komodo-periphery📝 Adjust the container resource limits for your needs. Parameters: cpus:, mem_limit:.
The file describes the operation and interaction of three services:
komodo_db- the MongoDB database;komodo_core- Komodo itself (the system core);komodo_periphery- the Komodo agent that has privileged access todocker.sockfor managing Docker on this host.
Now create a file with environment variables for the Komodo services:
vim ./.envAnd fill it in:
# https://github.com/moghtech/komodo/blob/main/compose/compose.env
TZ=Europe/Moscow
COMPOSE_KOMODO_IMAGE_TAG=2.0
KOMODO_INIT_ADMIN_USERNAME=komodo
KOMODO_INIT_ADMIN_PASSWORD=
KOMODO_DB_USERNAME=komodo
KOMODO_DB_PASSWORD=
KOMODO_PASSKEY=
KOMODO_HOST=https://komodo.home.lan
KOMODO_TITLE=Komodo
KOMODO_FIRST_SERVER=https://komodo-periphery:8120
KOMODO_FIRST_SERVER_NAME=komodo.home.lan
KOMODO_DISABLE_CONFIRM_DIALOG=true
KOMODO_MONITORING_INTERVAL="15-sec"
KOMODO_RESOURCE_POLL_INTERVAL="1-hr"
BLOG_KOMODO_WEBHOOK_SECRET=
KOMODO_JWT_SECRET=
KOMODO_JWT_TTL="1-day"
KOMODO_LOCAL_AUTH=true
KOMODO_DISABLE_USER_REGISTRATION=false
KOMODO_ENABLE_NEW_USERS=false
KOMODO_DISABLE_NON_ADMIN_CREATE=false
KOMODO_TRANSPARENT_MODE=false
KOMODO_LOGGING_PRETTY=false
KOMODO_PRETTY_STARTUP_CONFIG=false
PERIPHERY_ROOT_DIRECTORY=/opt/komodo/periphery
# PERIPHERY_STACK_DIR=/opt/komodo/stacks
PERIPHERY_PASSKEYS=${KOMODO_PASSKEY}
PERIPHERY_DISABLE_TERMINALS=false
PERIPHERY_SSL_ENABLED=true
PERIPHERY_INCLUDE_DISK_MOUNTS=/etc/hostname
PERIPHERY_LOGGING_PRETTY=false
PERIPHERY_PRETTY_STARTUP_CONFIG=falseHere we need to set our own values for passwords/secrets. Use the pwgen utility again:
sed -iE "s/^KOMODO_INIT_ADMIN_PASSWORD=.*/KOMODO_INIT_ADMIN_PASSWORD=$(pwgen -s 32 1)/" ./.env
sed -iE "s/^KOMODO_DB_PASSWORD=.*/KOMODO_DB_PASSWORD=$(pwgen -s 32 1)/" ./.env
sed -iE "s/^KOMODO_PASSKEY=.*/KOMODO_PASSKEY=$(pwgen -s 64 1)/" ./.env
sed -iE "s/^BLOG_KOMODO_WEBHOOK_SECRET=.*/BLOG_KOMODO_WEBHOOK_SECRET=$(pwgen -s 64 1)/" ./.env
sed -iE "s/^KOMODO_JWT_SECRET=.*/KOMODO_JWT_SECRET=$(pwgen -s 64 1)/" ./.envAfter that, we can start the komodo service:
docker compose up -d && docker compose logs -f
After a successful start, return to the browser and go to https://komodo.home.lan, where we see the Komodo control panel login without any TLS certificate complaints:

Enter the login komodo and the password from the KOMODO_INIT_ADMIN_PASSWORD variable:
grep ADMIN_PASSWORD ./.env
The interface is user-friendly, with a pleasant and modern design.
Creating a compose stack for nginx - files on the server
Go to “Stacks” –> “New Stack”:

Specify the name nginx, and choose our local komodo.home.lan as the server:

☝️ It is important that the stack name matches the name of the directory with the project’s compose files in /opt/komodo/periphery/stacks. This is why we created exactly this structure.
Enter the nginx stack:

And on the “Config” tab, choose the “Files On Server” mode:

📝 This mode allows you to pull existing project files into the Komodo “Stack” entity.
Save the changes:

And since our nginx is already running, the stack will automatically switch to the “RUNNING” status:

If you see something like this:

It means you specified the wrong stack name, or mounted the working directory of the periphery container incorrectly:
☝️ The agent working directory (periphery) is specified in the PERIPHERY_ROOT_DIRECTORY variable and must match the path specified there on the host itself and in the container. In my example: /opt/komodo/periphery:/opt/komodo/periphery.
You can change stack parameters directly in the Komodo web interface on the “Info” tab (in “Files On Server” mode). After changes, click “Save” and then “Redeploy” to apply the updated parameters.
Server resources can be managed both inside the stack itself and in the “Servers” –> “<server_name>” section:

Very visual, very convenient 👍.
Configuring the DNS server - Technitium

Any lab is considered incomplete if it does not have its own DNS server.
Creating a compose stack for technitium - files on the server
Return to the “Komodo” web panel –> “Stacks” –> “New Stack”. Specify the name technitium and choose our local server komodo.home.lan.
Then enter the stack, and on the “Config” tab choose the “UI Defined” mode:

Fill the “Compose File” field with this content:
---
# https://github.com/TechnitiumSoftware/DnsServer/blob/master/docker-compose.yml
networks:
komodo_net:
external: true
services:
technitium:
image: technitium/dns-server:${DNS_SERVER_IMAGE_TAG:-latest}
container_name: technitium
restart: unless-stopped
stop_grace_period: 1m
cpus: 2
mem_limit: 2G
hostname: technitium
sysctls:
- net.ipv4.ip_local_port_range=1024 65535
env_file: ./.env
volumes:
- ./data/:/etc/dns/
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
expose:
- 5380/tcp # DNS web console (HTTP)
# - 53443/tcp # DNS web console (HTTPS)
- 53/udp # DNS service
- 53/tcp # DNS service
# - 853/udp # DNS-over-QUIC service
# - 853/tcp # DNS-over-TLS service
# - 443/udp # DNS-over-HTTPS service (HTTP/3)
# - 443/tcp # DNS-over-HTTPS service (HTTP/1.1, HTTP/2)
# - 80/tcp # DNS-over-HTTP service (use with reverse proxy or certbot certificate renewal)
# - 8053/tcp # DNS-over-HTTP service (use with reverse proxy)
# - 67/udp # DHCP service
dns:
- 10.51.51.50
- 8.8.8.8
networks:
komodo_net:
ipv4_address: 10.51.51.54
aliases:
- technitium.home.lan
- technitium-dns
- technitium📝 Adjust the container resource limits for your needs. Parameters: cpus:, mem_limit:.
Scroll down to the “Environment” field and fill it in as follows:
# https://github.com/TechnitiumSoftware/DnsServer/blob/master/DockerEnvironmentVariables.md
DNS_SERVER_IMAGE_TAG=14.3.0
DNS_SERVER_ADMIN_PASSWORD=4RyYdGWcpkdGFj6anL64JxtbKwpQheqC
DNS_SERVER_DOMAIN=technitium.home.lan
DNS_SERVER_PREFER_IPV6=false
DNS_SERVER_RECURSION=AllowOnlyForPrivateNetworks
DNS_SERVER_FORWARDERS=8.8.8.8, 9.9.9.9
💡 Tip
The password for the DNS_SERVER_ADMIN_PASSWORD variable can also be generated with pwgen:
pwgen -s 32 1Click “Save”:

And start it by clicking the “Deploy” button:

Go to the “Log” tab to see the container output. If everything is OK, you should see the following:

You can change stack parameters directly in the Komodo web interface on the “Config” tab (in “UI Defined” mode). After changes, click “Save” and then “Redeploy” to apply the updated parameters.
☝️ It is important to understand that when a stack is deleted in Komodo, files from the host are not deleted.
After a successful start, go to the browser and open https://technitium.home.lan. The DNS management web interface should also open without problems:

Login admin; take the password from the DNS_SERVER_ADMIN_PASSWORD variable.
After logging in, immediately enable the dark theme 🌚:

This DNS server is very convenient to manage and has quite broad functionality: zones, cache, recursion, and so on.
Creating the home.lan zone and records for it
For convenience in our lab, create a separate zone for the home.lan domain.
Go to the “Zones” tab and click “Add Zone”:

Specify the home.lan zone, leave the “Primary zone (default)” checkbox enabled, and click “Add”:

After creation, we will land in the settings for the new zone. Click “Add Record”, specify the * character as the name, and 127.0.0.1 or the physical address where nginx-proxy-manager listens on port 443 in the address field.

Click “Save”.
📝 The * character works as follows: when any subdomain in the home.lan zone is accessed, the DNS server will resolve it to the nginx-proxy-manager address (127.0.0.1 in this example), which in turn will proxy to the service/container we need based on SNI. This simplifies our work in the home.lan zone, limiting us to adding only a proxy record in nginx, without constantly adding A records. Of course, you are free to configure DNS however you like. This is just my example. But for all this to work, you need to configure your DNS client. See below: “Configuring the DNS client”.
After a minute or two, check it from the host terminal using the dig utility:
dig @10.51.51.50 r4ven.me +noall +answer
dig @10.51.51.50 test.home.lan +noall +answerYou should see this:

This DNS server can do many things. I recommend exploring its functionality in your spare time 👨💻.
Checking the status of lab resources
Perform a small check that everything is in place and everything works after finishing the compose services configuration:
docker ps; docker image ls; docker network ls
Configuring autostart for key services
Since our lab rests on three pillars (proxy, Docker manager, and DNS server), let’s configure their autostart with Systemd so that they reliably start when the system boots.
Nginx Proxy Manager
Feel free to paste the whole block into the terminal (this is one here doc command):
cat << EOF | sudo tee /etc/systemd/system/komodo-proxy.service
[Unit]
Description=Nginx Proxy Manager
Requires=docker.service
After=docker.service
[Service]
Restart=always
RestartSec=5
User=root
Group=root
WorkingDirectory=/opt/komodo/periphery/stacks/nginx
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
EOFKomodo
cat << EOF | sudo tee /etc/systemd/system/komodo-core.service
[Unit]
Description=Komodo docker stack services
Requires=docker.service
After=docker.service
[Service]
Restart=always
RestartSec=5
User=root
Group=root
WorkingDirectory=/opt/komodo
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
EOFTechnitium
cat << EOF | sudo tee /etc/systemd/system/komodo-dns.service
[Unit]
Description=Technitium DNS server
Requires=docker.service
After=docker.service
[Service]
Restart=always
RestartSec=5
User=root
Group=root
WorkingDirectory=/opt/komodo/periphery/stacks/technitium
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.target
EOFStarting and checking
Update the Systemd configuration and try to start the services while enabling autostart:
sudo systemctl daemon-reload
sudo systemctl enable --now komodo-{proxy,core,dns}
systemctl list-units | grep -E 'komodo.*.service' | column -t
systemctl status komodo-{proxy,core,dns} | grep -B3 'Active:'
Configuring the DNS client
For convenient operation of our lab, I recommend configuring local DNS queries to be sent to our Technitium server.
Resolver configuration depends on your system and network configuration.
systemd-resolved
Below I will show an example configuration using systemd-resolved.
⚠️ Please approach name resolution configuration consciously. Before continuing, you need to make sure who exactly manages the list of servers for resolving on your system. For example, in Linux Mint, the server address to which DNS queries should be sent may be managed by NetworkManager, which can override settings from other programs.
Back up the DNS configs and create a new minimal systemd-resolved config:
sudo mv -v /etc/resolv.conf{,.backup}
sudo ln -sv /run/systemd/resolve/stub-resolv.conf /etc/resolv.conf
sudo mv -v /etc/systemd/resolved.conf{,_backup}
cat << EOF | sudo tee /etc/systemd/resolved.conf
[Resolve]
DNS=10.51.51.50
FallbackDNS=192.168.1.1 8.8.8.8
Domains=~.
EOFRestart the service:
sudo systemctl restart systemd-resolved
systemctl status systemd-resolved
resolvectl
Check DNS operation without explicitly specifying the technitium address:
dig r4ven.me +noall +answer +identify
dig test.home.lan +noall +answer +identify
It works!
NetworkManager
If your network is managed by NetworkManager, you can specify your DNS servers through the GUI:



Or through nmcli:
# View connections
nmcli connection show
# Set DNS
nmcli connection modify "Wired connection 1" \
ipv4.dns "10.51.51.50,192.168.1.1,8.8.8.8" \
ipv4.ignore-auto-dns yesAfter applying changes, it is advisable to restart the NetworkManager service:
sudo systemctl restart NetworkManagerCheck DNS operation without explicitly specifying the technitium address:
dig r4ven.me +noall +answer +identify
dig test.home.lan +noall +answer +identify
Everything looks OK 😌. But I recommend the systemd-resolved option more.
Adding external servers to Komodo
One of Komodo’s key features is the ability to orchestrate containers that run on remote servers.
Next, I will show an example of configuring and running the agent (periphery) on a remote machine with the IP 192.168.122.31.
Installing komodo-periphery on a remote host for management through local Komodo
Of course, Docker Engine must be installed on the remote host:
sudo curl https://get.docker.com/ | bashCreate a working directory, set permissions, and create a compose file:
sudo mkdir -p /opt/periphery && sudo chmod 700 /opt/periphery
sudo vim /opt/periphery/compose.yamlThen fill it in:
---
services:
komodo_periphery:
image: ghcr.io/moghtech/komodo-periphery:2.0
labels:
komodo.skip:
container_name: komodo-periphery
stop_grace_period: 1m
cpus: 1
mem_limit: 1G
hostname: komodo-periphery
restart: on-failure
env_file: ./.env
ports:
- 192.168.122.31:8120:8120
volumes:
- ${PERIPHERY_ROOT_DIRECTORY}:${PERIPHERY_ROOT_DIRECTORY}
- /var/run/docker.sock:/var/run/docker.sock
- /proc:/proc
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro☝️ Be sure to replace the address 192.168.122.31 with your own.
Now create and fill in the environment variables:
sudo vim /opt/periphery/.envTZ=Europe/Moscow
PERIPHERY_ROOT_DIRECTORY=/opt/periphery
PERIPHERY_PASSKEYS=
PERIPHERY_DISABLE_TERMINALS=false
PERIPHERY_SSL_ENABLED=true
PERIPHERY_INCLUDE_DISK_MOUNTS=/etc/hostname
PERIPHERY_LOGGING_PRETTY=false
PERIPHERY_PRETTY_STARTUP_CONFIG=false
PERIPHERY_BIND_IP=0.0.0.0☝️ Important
In the PERIPHERY_PASSKEYS variable, be sure to specify the same key that is specified in .env for the Komodo server. Authorization will happen through it. Connection and management are performed over HTTPS/Websockets.
Now create a Systemd unit for starting Periphery automatically when the OS boots:
sudo vim /etc/systemd/system/komodo-periphery.service[Unit]
Description=Komodo periphery docker stack services
Requires=docker.service
After=docker.service
[Service]
Restart=always
RestartSec=5
User=root
Group=root
WorkingDirectory=/opt/periphery
ExecStartPre=/usr/bin/sleep 5
ExecStart=/usr/bin/docker compose up
ExecStop=/usr/bin/docker compose down
[Install]
WantedBy=multi-user.targetReread the Systemd config and start our service:
sudo systemctl daemon-reload
sudo systemctl enable --now komodo-periphery
systemctl status komodo-periphery
sudo docker compose -f /opt/periphery/compose.yaml logs -f komodo_peripheryIf we see this, everything is OK:

Now return to the Komodo interface: “Servers” –> “New Server”, specify a name, for example rustfs.home.lan:

Here enable the “Enabled” server option and specify its address:port. In my case, this is https://192.168.122.31:8120:

☝️ Be sure to specify https:// in the address.
Click “Save”, and after a successful connection you will see:

☝️ Important
The remote host:port must be reachable from your network where the Komodo container lives.
Creating a compose stack - RustFS (S3 storage)
Overall, our lab is already operational and ready to perform its tasks. As an example, let’s add one more stack: RustFS, an S3-compatible storage that acts as an alternative to the infamous MinIO.
Go to “Stacks” –> “New Stack”, specify the name rustfs, and choose our remote server rustfs.home.lan:

Next, choose the UI Defined mode and fill in the “Compose File” fields in the same way:
---
# https://github.com/rustfs/rustfs/blob/main/docker-compose-simple.yml
# https://github.com/rustfs/rustfs/blob/main/docker-compose.yml
services:
rustfs:
image: rustfs/rustfs:1.0.0-alpha.90
container_name: rustfs
restart: unless-stopped
stop_grace_period: 30s
cpus: 2
mem_limit: 2G
user: "root:root"
env_file: ./.env
healthcheck:
test:
[
"CMD",
"sh", "-c",
"curl -f http://127.0.0.1:9000/health && curl -f http://127.0.0.1:9001/rustfs/console/health"
]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
ports:
- "192.168.122.31:9000:9000" # S3 API port
- "192.168.122.31:9001:9001" # Console port
volumes:
- ./data/storage/:/data/
- ./data/logs/:/app/logs/
- ./data/certs/:/opt/tls/ # TLS configuration, you should create tls directory and put your tls files in it and then specify the path here
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro☝️ Do not forget to specify your own address instead of 192.168.122.31.
and “Environment”:
# https://docs.rustfs.com/installation/docker/#complete-parameter-configuration-example
RUSTFS_UID="root"
RUSTFS_GID="root"
TZ=Europe/Moscow
# RUSTFS_VOLUMES=/data/rustfs{0..3} # Define 4 storage volumes
RUSTFS_ADDRESS=0.0.0.0:9000
RUSTFS_CONSOLE_ADDRESS=0.0.0.0:9001
RUSTFS_CONSOLE_ENABLE=true
RUSTFS_CORS_ALLOWED_ORIGINS=*
RUSTFS_CONSOLE_CORS_ALLOWED_ORIGINS=*
RUSTFS_ACCESS_KEY=ivan
RUSTFS_SECRET_KEY=3QuZHlIh6KDtnX0SBElGa8nI2xZAtSNb
RUSTFS_OBS_LOGGER_LEVEL=info
RUSTFS_TLS_PATH=/opt/tls
# RUSTFS_OBS_ENDPOINT=http://otel-collector:4318In the RUSTFS_ACCESS_KEY variable, specify the administrator login, and set the password in RUSTFS_SECRET_KEY. You can also generate it with pwgen:
pwgen -s 32 1Now “Save” and “Deploy”:

It is time to configure proxying. Go to our Nginx: “Proxy Hosts” –> “Add Proxy Host”.
Create a record for the RustFS admin panel:
- Domains:
rustfs.home.lan - Forward IP:
192.168.122.31 - Forward Port:
9001 - SSL certificate:
_wildcard.home.lan - Force SSL - yes
- HTTP/2 Support - yes
Then create a record for S3 itself:
- Domains:
s3.home.lan - Forward IP:
192.168.122.31 - Forward Port:
9000 - SSL certificate:
_wildcard.home.lan - Force SSL - yes
- HTTP/2 Support - yes

Go to https://rustfs.home.lan, enter the login (RUSTFS_ACCESS_KEY) and password (RUSTFS_SECRET_KEY):


You can create buckets:

And access them at s3.home.lan. For example, using the rclone utility, about which I recently wrote an article on mounting remote storage:
sudo apt install -y rclone
mkdir -vp ~/.config/rclone
cat << EOF > ~/.config/rclone/rclone.conf
[rustfs]
type = s3
provider = Minio
endpoint = https://s3.home.lan
access_key_id = ivan
secret_access_key = 3QuZHlIh6KDtnX0SBElGa8nI2xZAtSNb
# force_path_style = true
# no_check_bucket = true
EOFCheck:
rclone ls rustfs:
rclone mkdir rustfs:doc
rclone copy /usr/share/doc/docker-ce rustfs:doc
rclone ls rustfs:doc

It works😌
Afterword
Phew, as usual, the path was thorny, but we made it 😉👨💻.
Now we have a full-fledged lab where you can develop, test, and operate various services with much greater convenience 😌.
In the next note, I will show how to create stacks using the third option: through Git repositories. I will show how to safely store env files in Git and automate the deployment procedure on push of changes to the repo.
I also have a ready-made Ansible playbook for deploying komodo-periphery to remote hosts. I will also publish it as a separate note. Do not miss it!
Join our Telegram channel and the Raven chat there as well. We have a friendly (I keep an eye on this) community of penguins 🐧🐧🐧 there. You can also freely ask questions about the blog materials there.
Thank you for reading. See you! 👋
Useful materials
- Official Nginx Proxy Manager website
- Nginx Proxy Manager GitHub repository
- Official Nginx Proxy Manager installation documentation
- Official Komodo website
- Komodo GitHub repository
- Official Komodo documentation
- mongo.compose.yaml for Komodo
- compose.env for Komodo
- Komodo Import - Quickstart
- Official Technitium DNS website
- Technitium DNS GitHub repository
- docker-compose.yml for Technitium DNS
- Docker environment variables for Technitium DNS
👨💻And…
Don’t forget about our Telegram channel 📱 and chat
Or maybe you want to become a co-author? Then click here🔗
💬 All the best ✌️
That should be it. If not, check the logs 🙂


