Remove dups

This commit is contained in:
Mike McFetridge
2026-07-24 15:42:46 -04:00
parent 84e1ead50c
commit b78378565a
345 changed files with 0 additions and 40650 deletions
@@ -1,20 +0,0 @@
---
- name: "Activate Checkmk changes"
hosts: localhost
gather_facts: false
vars_files:
- secrets.yaml
vars:
server_url: "http://192.168.2.4:5000"
site: "cmk"
tasks:
- name: "Start activation on a specific site"
checkmk.general.activation:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
force_foreign_changes: 'true'
sites:
- "{{ site }}"
@@ -1,16 +0,0 @@
---
- name: Install Checkmk agent on all hosts
hosts: all
become: true
roles:
- checkmk.general.agent
vars:
checkmk_agent_version: "2.4.0p15"
checkmk_agent_server: << checkmk_server >>
checkmk_agent_server_protocol: << checkmk_protocol >>
checkmk_agent_site: << checkmk_site >>
checkmk_agent_auto_activate: << checkmk_auto_activate >>
checkmk_agent_tls: << checkmk_tls >>
checkmk_agent_user: << checkmk_user >>
checkmk_agent_pass: << checkmk_pass >>
checkmk_agent_host_name: << checkmk_host >>
@@ -1,82 +0,0 @@
{
"slug": "checkmk-install-agent",
"kind": "ansible",
"metadata": {
"name": "Install Checkmk Agent",
"description": "Installs and registers the Checkmk monitoring agent on target hosts using the checkmk.general.agent Ansible role.",
"tags": [],
"icon": {
"provider": "selfhst",
"id": "checkmk"
},
"draft": false,
"version": {
"name": "2.4.0",
"source_dep_name": "manual/checkmk-install-agent"
}
},
"variables": [
{
"title": "Checkmk Configuration",
"name": "checkmk",
"items": [
{
"name": "checkmk_server",
"type": "str",
"title": "Checkmk Server",
"required": true
},
{
"name": "checkmk_protocol",
"type": "str",
"title": "Protocol",
"required": true,
"default": "https",
"config": {
"placeholder": "https"
}
},
{
"name": "checkmk_site",
"type": "str",
"title": "Checkmk Site",
"required": true,
"default": "cmk",
"config": {
"placeholder": "cmk"
}
},
{
"name": "checkmk_auto_activate",
"type": "bool",
"title": "Auto Activate Agent",
"required": false
},
{
"name": "checkmk_tls",
"type": "bool",
"title": "TLS",
"required": false
},
{
"name": "checkmk_user",
"type": "str",
"title": "Checkmk Automation User",
"required": true
},
{
"name": "checkmk_pass",
"type": "secret",
"title": "Automation Password",
"required": true
},
{
"name": "checkmk_host",
"type": "str",
"title": "Checkmk Host Name",
"required": true
}
]
}
]
}
@@ -1,18 +0,0 @@
---
- name: Manage Checkmk host
hosts: all
gather_facts: false
tasks:
- name: "Create or update host in Checkmk"
checkmk.general.host:
server_url: "<< checkmk_protocol >>://<< checkmk_server >>"
site: << checkmk_site >>
automation_user: << checkmk_user >>
automation_secret: << checkmk_pass >>
name: << host_name >>
attributes:
ipaddress: << host_ip >>
folder: << host_folder >>
state: "present"
delegate_to: localhost
run_once: true
@@ -1,94 +0,0 @@
{
"slug": "checkmk-manage-host",
"kind": "ansible",
"metadata": {
"name": "Manage Checkmk Host",
"description": "Manages host entries in Checkmk monitoring using the checkmk.general.host Ansible module. Creates or updates host configuration in your Checkmk instance.",
"tags": [],
"icon": {
"provider": "selfhst",
"id": "checkmk"
},
"draft": false,
"version": {
"name": "2.4.0",
"source_dep_name": "manual/checkmk-manage-host"
}
},
"variables": [
{
"title": "Checkmk Configuration",
"name": "checkmk",
"items": [
{
"name": "checkmk_server",
"type": "str",
"title": "Checkmk Server",
"required": true
},
{
"name": "checkmk_protocol",
"type": "str",
"title": "Protocol",
"required": true,
"default": "https",
"config": {
"placeholder": "https"
}
},
{
"name": "checkmk_site",
"type": "str",
"title": "Checkmk Site",
"required": true,
"default": "cmk",
"config": {
"placeholder": "cmk"
}
},
{
"name": "checkmk_user",
"type": "str",
"title": "Checkmk Automation User",
"required": true
},
{
"name": "checkmk_pass",
"type": "secret",
"title": "Automation Password",
"required": true
}
]
},
{
"title": "Host Configuration",
"name": "host",
"items": [
{
"name": "host_name",
"type": "str",
"title": "Host Name",
"required": true,
"description": "Hostname to add to Checkmk"
},
{
"name": "host_ip",
"type": "str",
"title": "Host IP",
"required": true,
"description": "IP address of the host"
},
{
"name": "host_folder",
"type": "str",
"title": "Folder Path",
"required": true,
"default": "/",
"config": {
"placeholder": "/"
}
}
]
}
]
}
@@ -1,20 +0,0 @@
# to run: ansible-playbook -i ./inventory.ini install-agent.yaml --ask-become-pass
---
- name: "Install Checkmk agent on all hosts"
hosts: all
become: yes
roles:
- checkmk.general.agent
vars:
checkmk_agent_version: "2.3.0p7"
#checkmk_agent_server: "192.168.2.4:5000"
checkmk_agent_server: "checkmk.mikemcfetridge.com"
checkmk_agent_server_protocol: https
checkmk_agent_site: "cmk"
checkmk_agent_auto_activate: true
checkmk_agent_tls: "true" # NOTE: Register Agent to enable TLS
checkmk_agent_user: "{{ automation_user }}"
checkmk_agent_pass: "{{ automation_secret }}"
checkmk_agent_host_name: "{{ ansible_hostname }}" # NOTE: Required to replace FQDN with hostname only
vars_files:
- secrets.yaml
-23
View File
@@ -1,23 +0,0 @@
[servers]
192.168.2.1
192.168.2.2
192.168.2.3
192.168.2.4
192.168.2.5
192.168.2.6
192.168.2.7
192.168.2.8
192.168.2.9
192.168.2.10
192.168.2.11
192.168.2.12
192.168.2.13
192.168.2.14
192.168.2.16
192.168.2.17
192.168.2.18
192.168.2.19
192.168.2.20
192.168.2.22
192.168.2.23
192.168.2.24
-21
View File
@@ -1,21 +0,0 @@
[servers]
ansible-server.home.mikemcfetridge.com
arrsserver.home.mikemcfetridge.com
beszel-server.home.mikemcfetridge.com
checkmk.home.mikemcfetridge.com
dockerapps.home.mikemcfetridge.com
gitea-server.home.mikemcfetridge.com
glance-server.home.mikemcfetridge.com
immich-wallabag.home.mikemcfetridge.com
linkwarden.home.mikemcfetridge.com
netbird-1.home.mikemcfetridge.com
netbird-2.home.mikemcfetridge.com
npm-server.home.mikemcfetridge.com
pihole-server.home.mikemcfetridge.com
pve.home.mikemcfetridge.com
pve2.home.mikemcfetridge.com
retrogaming-server.home.mikemcfetridge.com
searxng-server.home.mikemcfetridge.com
technitiumdns-server.home.mikemcfetridge.com
vaultwarden-server.home.mikemcfetridge.com
wireguard-server.home.mikemcfetridge.com
@@ -1,25 +0,0 @@
---
- name: "Manage Checkmk rules"
hosts: localhost
gather_facts: false
vars_files:
- secrets.yaml
vars:
server_url: "http://192.168.2.4:5000"
site: "cmk"
tasks:
- name: Get a rule with a particular rule id
ansible.builtin.debug:
msg: "Rule: {{ extensions | to_nice_yaml }}"
vars:
extensions: "{{
lookup('checkmk.general.rule',
rule_id='checkmk-rule-id',
server_url=server_url,
site=site,
automation_user=automation_user,
automation_secret=automation_secret,
validate_certs=False
)
}}"
-286
View File
@@ -1,286 +0,0 @@
---
- name: "Manage Checkmk hosts"
hosts: localhost
gather_facts: false
vars_files:
- secrets.yaml
vars:
server_url: "http://192.168.2.4:5000"
site: "cmk"
tasks:
- name: "Create host - PVE01"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "pve01"
attributes:
ipaddress: "192.168.2.1"
folder: "/"
state: "present"
- name: "Create host - PVE02"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "pve02"
attributes:
ipaddress: "192.168.2.2"
folder: "/"
state: "present"
- name: "Create host - beszel"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Beszel"
attributes:
ipaddress: "192.168.2.3"
folder: "/"
state: "present"
- name: "Create host - Checkmk"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Checkmk"
attributes:
ipaddress: "192.168.2.4"
folder: "/"
state: "present"
- name: "Create host - komodo"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "komodo"
attributes:
ipaddress: "192.168.2.5"
folder: "/"
state: "present"
- name: "Create host - Datacenter"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Datacenter"
attributes:
ipaddress: "192.168.2.6"
folder: "/"
state: "present"
- name: "Create host - DockerApps"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "DockerApps"
attributes:
ipaddress: "192.168.2.7"
folder: "/"
state: "present"
- name: "Create host - PIHOLE"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "PiHole"
attributes:
ipaddress: "192.168.2.8"
folder: "/"
state: "present"
- name: "Create host - TECHNITIUMDNS"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "TechnitiumDNS"
attributes:
ipaddress: "192.168.2.9"
folder: "/"
state: "present"
- name: "Create host - Wireguard"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Wireguard"
attributes:
ipaddress: "192.168.2.10"
folder: "/"
state: "present"
- name: "Create host - Ansible"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Ansible"
attributes:
ipaddress: "192.168.2.11"
folder: "/"
state: "present"
- name: "Create host - LinkWarden"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "LinkWarden"
attributes:
ipaddress: "192.168.2.12"
folder: "/"
state: "present"
- name: "Create host - Immich"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Immich"
attributes:
ipaddress: "192.168.2.13"
folder: "/"
state: "present"
- name: "Create host - RetroGaming"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "RetroGaming"
attributes:
ipaddress: "192.168.2.14"
folder: "/"
state: "present"
- name: "Create host - MediaServer"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "MediaServer"
attributes:
ipaddress: "192.168.2.16"
folder: "/"
state: "present"
- name: "Create host - NetBird1"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "NetBird1"
attributes:
ipaddress: "192.168.2.17"
folder: "/"
state: "present"
- name: "Create host - NetBird2"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "NetBird2"
attributes:
ipaddress: "192.168.2.18"
folder: "/"
state: "present"
- name: "Create host - VaultWarden"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "VaultWarden"
attributes:
ipaddress: "192.168.2.19"
folder: "/"
state: "present"
- name: "Create host - Glance"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Glance"
attributes:
ipaddress: "192.168.2.20"
folder: "/"
state: "present"
- name: "Create host - NPM"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Npm"
attributes:
ipaddress: "192.168.2.22"
folder: "/"
state: "present"
- name: "Create host - Searxng"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Searxng"
attributes:
ipaddress: "192.168.2.23"
folder: "/"
state: "present"
- name: "Create host - Gitea"
checkmk.general.host:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
name: "Gitea"
attributes:
ipaddress: "192.168.2.24"
folder: "/"
state: "present"
- name: "Start activation on a specific site"
checkmk.general.activation:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
force_foreign_changes: 'true'
sites:
- "{{ site }}"
@@ -1,71 +0,0 @@
---
- name: "Manage Checkmk rules"
hosts: localhost
gather_facts: false
vars_files:
- secrets.yaml
vars:
server_url: "checkmk.home.arpa"
site: "cmk"
tasks:
- name: Create DNS Check Rule
checkmk.general.rule:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
ruleset: "active_checks:dns"
rule:
properties: {
"comment": "Ansible managed",
"description": "DNS DNS Monitoring",
"disabled": false,
}
conditions: {
"host_label_groups": [],
"host_name": {
"match_on": [
"your-dns-container-host"
],
"operator": "one_of"
},
"host_tags": [],
"service_label_groups": []
}
"value_raw": {
"hostname": "hostname-to-query",
"server": "dns-server-ip",
"expected_addresses_list": [
"expected-ip-address"
]
}
location:
folder: "/"
position: "top"
state: "present"
- name: Create NVME Temperature override rule
checkmk.general.rule:
server_url: "{{ server_url }}"
site: "{{ site }}"
automation_user: "{{ automation_user }}"
automation_secret: "{{ automation_secret }}"
ruleset: "checkgroup_parameters:temperature"
rule:
conditions:
host_label_groups: []
host_tags: []
service_description:
match_on:
- "DRIVE MODEL NAME*"
operator: "one_of"
service_label_groups: []
location:
folder: "/"
position: "top"
properties:
description: "NVME Temperature override"
disabled: false
value_raw: "{'levels': (60.0, 80.0)}"
state: present
-3
View File
@@ -1,3 +0,0 @@
---
automation_user: "ansible"
automation_secret: "Px40If4VUvvA35"
@@ -1,52 +0,0 @@
---
- name: Enable Docker TLS
hosts: all
become: true
vars:
certs_path: "<< certs_path >>"
tasks:
- name: Check if docker certs are existing
ansible.builtin.stat:
path: {{ certs_path }}
register: certs_dir
- name: Fail if docker certs are not existing
ansible.builtin.fail:
msg: "Docker certificates are not existing in /root/docker-certs."
when: not certs_dir.stat.exists
- name: Get machine's primary internal ip address from eth0 interface
ansible.builtin.setup:
register: ip_address
- name: Set machine's primary internal ip address
ansible.builtin.set_fact:
ip_address: {{ ip_address.ansible_facts.ansible_default_ipv4.address }}
- name: Check if ip_address is a valid ip address
ansible.builtin.assert:
that:
- ip_address is match("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")
fail_msg: "ip_address is not a valid ip address."
success_msg: "ip_address is a valid ip address."
- name: Change docker daemon to use certs
ansible.builtin.lineinfile:
path: /lib/systemd/system/docker.service
line: >
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
-H tcp://{{ ip_address }}:2376 --tlsverify --tlscacert={{ certs_path }}/ca.pem
--tlscert={{ certs_path }}/server-cert.pem --tlskey={{ certs_path }}/server-key.pem
regexp: '^ExecStart='
state: present
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
- name: Restart docker daemon
ansible.builtin.systemd:
name: docker
state: restarted
enabled: true
@@ -1,37 +0,0 @@
{
"slug": "docker-certs-enable",
"kind": "ansible",
"metadata": {
"name": "Enable Docker TLS",
"description": "Enables TLS on the Docker daemon using existing certificates. Configures Docker for secure remote API access over an encrypted connection.",
"tags": [],
"icon": {
"provider": "simple-icons",
"id": "docker"
},
"draft": true,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/docker-certs-enable"
}
},
"variables": [
{
"title": "Certificate Configuration",
"name": "certificates",
"items": [
{
"name": "certs_path",
"type": "str",
"title": "Certs Path",
"required": false,
"default": "/root/docker-certs",
"description": "Path where certificates are stored",
"config": {
"placeholder": "/root/docker-certs"
}
}
]
}
]
}
@@ -1,158 +0,0 @@
---
- name: Generate Docker TLS certificates
hosts: all
become: true
vars:
certs_path: "<< certs_path >>"
cert_validity_days: << cert_validity_days >>
cn_domain: "<< cn_domain >>"
tasks:
- name: Check if docker certs are existing
ansible.builtin.stat:
path: {{ certs_path }}
register: certs_dir
- name: Create docker certs directory (if needed)
ansible.builtin.file:
path: {{ certs_path }}
state: directory
mode: '0700'
when: not certs_dir.stat.exists
- name: Check if docker certs directory is empty
ansible.builtin.command: ls -A {{ certs_path }}
register: certs_list
when: certs_dir.stat.exists
changed_when: false
ignore_errors: true
- name: Fail if docker certs already exist
ansible.builtin.fail:
msg: "Docker certificates already exist in /root/docker-certs."
when: certs_list.stdout | default('') != ''
- name: Get machine's primary internal ip address from eth0 interface
ansible.builtin.setup:
register: ip_address
- name: Set machine's primary internal ip address
ansible.builtin.set_fact:
ip_address: {{ ip_address.ansible_facts.ansible_default_ipv4.address }}
- name: Check if ip_address is a valid ip address
ansible.builtin.assert:
that:
- ip_address is match("^(?:[0-9]{1,3}\\.){3}[0-9]{1,3}$")
fail_msg: "ip_address is not a valid ip address."
success_msg: "ip_address is a valid ip address."
- name: Generate CA private key
ansible.builtin.command:
cmd: >
openssl genrsa -out {{ certs_path }}/ca-key.pem 4096
args:
creates: {{ certs_path }}/ca-key.pem
- name: Generate CA certificate
ansible.builtin.command:
cmd: >
openssl req -sha256 -new -x509
-subj "/CN={{ cn_domain }}"
-days {{ cert_validity_days }}
-key {{ certs_path }}/ca-key.pem
-out {{ certs_path }}/ca.pem
args:
creates: {{ certs_path }}/ca.pem
- name: Generate server private key
ansible.builtin.command:
cmd: >
openssl genrsa -out {{ certs_path }}/server-key.pem 4096
creates: {{ certs_path }}/server-key.pem
- name: Generate server certificate signing request
ansible.builtin.command:
cmd: >
openssl req -sha256 -new
-subj "/CN={{ inventory_hostname }}"
-key {{ certs_path }}/server-key.pem
-out {{ certs_path }}/server.csr
creates: {{ certs_path }}/server.csr
- name: Generate server certificate extension file
ansible.builtin.shell: |
echo "subjectAltName = DNS:{{ inventory_hostname }},IP:{{ ip_address }},IP:127.0.0.1" >> {{ certs_path }}/extfile.cnf
echo "extendedKeyUsage = serverAuth" >> {{ certs_path }}/extfile.cnf
args:
creates: {{ certs_path }}/extfile.cnf
- name: Generate server certificate
ansible.builtin.command:
cmd: >
openssl x509 -req -days {{ cert_validity_days }} -sha256
-in {{ certs_path }}/server.csr
-CA {{ certs_path }}/ca.pem
-CAkey {{ certs_path }}/ca-key.pem
-CAcreateserial -out {{ certs_path }}/server-cert.pem
-extfile {{ certs_path }}/extfile.cnf
creates: {{ certs_path }}/server-cert.pem
- name: Generate client private key
ansible.builtin.command:
cmd: >
openssl genrsa -out {{ certs_path }}/key.pem 4096
creates: {{ certs_path }}/key.pem
- name: Generate client certificate signing request
ansible.builtin.command:
cmd: >
openssl req -sha256 -new
-subj "/CN=client"
-key {{ certs_path }}/key.pem
-out {{ certs_path }}/client.csr
creates: {{ certs_path }}/client.csr
- name: Generate client certificate extension file
ansible.builtin.shell: |
echo "extendedKeyUsage = clientAuth" >> {{ certs_path }}/client-extfile.cnf
args:
creates: {{ certs_path }}/client-extfile.cnf
- name: Generate client certificate
ansible.builtin.command:
cmd: >
openssl x509 -req -days {{ cert_validity_days }}
-sha256 -in {{ certs_path }}/client.csr
-CA {{ certs_path }}/ca.pem
-CAkey {{ certs_path }}/ca-key.pem
-CAcreateserial -out {{ certs_path }}/cert.pem
-extfile {{ certs_path }}/client-extfile.cnf
creates: {{ certs_path }}/cert.pem
- name: Remove client certificate signing request
ansible.builtin.file:
path: {{ certs_path }}/server.csr
state: absent
- name: Remove client certificate signing request
ansible.builtin.file:
path: {{ certs_path }}/client.csr
state: absent
- name: Remove server certificate extension file
ansible.builtin.file:
path: {{ certs_path }}/extfile.cnf
state: absent
- name: Remove client certificate extension file
ansible.builtin.file:
path: {{ certs_path }}/client-extfile.cnf
state: absent
- name: Set permissions for docker certs
ansible.builtin.file:
path: {{ certs_path }}
mode: '0700'
recurse: true
follow: true
@@ -1,63 +0,0 @@
{
"slug": "docker-certs",
"kind": "ansible",
"metadata": {
"name": "Generate Docker TLS Certificates",
"description": "Generates TLS certificates for the Docker daemon, including CA, server, and client certificates. Used to secure Docker remote API access.",
"tags": [],
"icon": {
"provider": "simple-icons",
"id": "docker"
},
"draft": true,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/docker-certs"
}
},
"variables": [
{
"title": "Certificate Configuration",
"name": "certificates",
"items": [
{
"name": "certs_path",
"type": "str",
"title": "Certs Path",
"required": false,
"default": "/root/docker-certs",
"description": "Path where certificates will be stored",
"config": {
"placeholder": "/root/docker-certs"
}
},
{
"name": "cert_validity_days",
"type": "int",
"title": "Validity Days",
"required": false,
"default": 3630,
"description": "Certificate validity period in days",
"config": {
"slider": true,
"min": 30,
"max": 3650,
"step": 30,
"placeholder": "3650",
"unit": "days"
}
},
{
"name": "cn_domain",
"type": "str",
"title": "CA Common Name",
"required": false,
"description": "Common Name (CN) for the CA certificate",
"config": {
"placeholder": "home.arpa.tld"
}
}
]
}
]
}
@@ -1,18 +0,0 @@
---
- name: Docker prune safe
hosts: all
become: true
gather_facts: false
tasks:
- name: Prune Docker resources with safe defaults
community.docker.docker_prune:
containers: << prune_stopped_containers | default(false) >>
images: true
images_filters:
dangling: << not (prune_include_unused_tagged_images | default(false)) >>
networks: << prune_unused_networks | default(false) >>
volumes: << prune_unused_volumes | default(false) >>
builder_cache: << prune_builder_cache | default(false) >>
builder_cache_keep_storage: "<< prune_keep_storage_mb | default(1024) >>M"
timeout: 300
@@ -1,2 +0,0 @@
collections:
- name: community.docker
@@ -1,83 +0,0 @@
{
"slug": "docker-cleanup",
"kind": "ansible",
"metadata": {
"name": "Docker Cleanup",
"description": "Safely prunes Docker resources on target hosts. Only removes resources that are explicitly enabled in the configuration, keeping a conservative default.",
"tags": [
"docker",
"cleanup",
"prune",
"maintenance"
],
"icon": {
"provider": "mdi",
"id": "broom",
"color": "fuchsia"
},
"draft": false,
"version": {
"name": "1",
"source_dep_name": "manual/docker-cleanup"
}
},
"variables": [
{
"title": "Prune Options",
"name": "prune",
"items": [
{
"name": "prune_include_unused_tagged_images",
"type": "bool",
"title": "Tagged Images",
"required": false,
"default": false
},
{
"name": "prune_stopped_containers",
"type": "bool",
"title": "Prune Stopped Containers",
"required": false,
"default": false
},
{
"name": "prune_unused_volumes",
"type": "bool",
"title": "Prune Unused Volumes",
"required": false,
"default": false
},
{
"name": "prune_unused_networks",
"type": "bool",
"title": "Prune Unused Networks",
"required": false,
"default": false
},
{
"name": "prune_builder_cache",
"type": "bool",
"title": "Prune Builder Cache",
"required": false,
"default": false
},
{
"name": "prune_keep_storage_mb",
"type": "int",
"title": "Keep Storage",
"required": false,
"default": 1024,
"description": "Minimum amount of cache/storage to keep when pruning (where supported)",
"config": {
"slider": true,
"min": 256,
"max": 102400,
"step": 256,
"placeholder": "1024",
"unit": "MB"
}
}
]
}
]
}
@@ -1,72 +0,0 @@
---
- name: Docker disk usage report
hosts: all
become: true
gather_facts: false
vars:
report_root: "/opt/docker-reports"
report_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
report_file_name: "docker_disk_usage_<< inventory_hostname >>_<< report_timestamp >>.txt"
tasks:
- name: Ensure report root exists
ansible.builtin.file:
path: "<< report_root >>"
state: directory
mode: "0750"
- name: Collect docker system df summary
ansible.builtin.command: docker system df
register: docker_df_summary
changed_when: false
- name: Collect docker system df verbose report
ansible.builtin.command: docker system df -v
register: docker_df_verbose
changed_when: false
when: include_verbose_report | default(false)
- name: Build report body
ansible.builtin.set_fact:
docker_disk_usage_report_body: |
Host: << inventory_hostname >>
Timestamp: {{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}
Docker system df:
<< docker_df_summary.stdout >>
<%- if include_verbose_report | default(false) %>
Docker system df -v:
<< docker_df_verbose.stdout >>
<%- endif %>
- name: Save local disk usage report
ansible.builtin.copy:
dest: "<< report_root >>/<< report_file_name >>"
content: "<< docker_disk_usage_report_body >>"
mode: "0640"
- name: Build Discord report content
ansible.builtin.set_fact:
discord_disk_report_content: |
<< discord_message_prefix | default('Docker Disk Usage Report') >>
```text
<< docker_disk_usage_report_body | truncate(1700, true, '...') >>
```
when:
- discord_enabled | default(false)
- (discord_webhook_url | default('')) | length > 0
- name: Send disk usage report to Discord webhook
ansible.builtin.uri:
url: "<< discord_webhook_url | default('') >>"
method: POST
body_format: json
body:
username: "<< discord_username | default('Docker Reporter') >>"
content: "<< discord_disk_report_content >>"
status_code: 204
when:
- discord_enabled | default(false)
- (discord_webhook_url | default('')) | length > 0
@@ -1,148 +0,0 @@
{
"slug": "docker-disk-usage-report",
"kind": "ansible",
"metadata": {
"name": "Docker Disk Usage Report",
"description": "Generates a Docker disk usage report on target hosts and optionally sends a summary to Discord via webhook.",
"tags": [
"docker",
"report",
"disk",
"monitoring"
],
"icon": {
"provider": "mdi",
"id": "chart-box",
"color": "blue"
},
"draft": true,
"version": {
"name": "1",
"source_dep_name": "manual/docker-disk-usage-report"
}
},
"variables": [
{
"title": "Report",
"name": "report",
"items": [
{
"name": "include_verbose_report",
"type": "bool",
"title": "Verbose Report",
"required": false,
"default": false,
"description": "Include docker system df -v output in local report"
}
]
},
{
"title": "Discord Webhook",
"name": "discord",
"items": [
{
"name": "discord_enabled",
"type": "bool",
"title": "Discord",
"required": false,
"default": false,
"description": "Send a short report to Discord webhook"
},
{
"name": "discord_webhook_url",
"type": "str",
"title": "Discord Webhook URL",
"required": false,
"default": ""
},
{
"name": "discord_username",
"type": "str",
"title": "Discord Username",
"required": false,
"default": "Docker Reporter",
"description": "Sender name for webhook message",
"config": {
"placeholder": "Docker Reporter"
}
},
{
"name": "discord_message_prefix",
"type": "str",
"title": "Discord Message Prefix",
"required": false,
"default": "Docker Disk Usage Report",
"description": "Prefix line sent before report content",
"config": {
"placeholder": "Docker Disk Usage Report"
}
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "docker_df_verbose",
"type": "str",
"title": "Docker DF Verbose",
"required": false,
"default": ""
},
{
"name": "report_file_name",
"type": "str",
"title": "Report File Name",
"required": false,
"default": ""
},
{
"name": "report_root",
"type": "str",
"title": "Report Root",
"required": false,
"default": "/reports",
"config": {
"placeholder": "/reports"
}
},
{
"name": "docker_disk_usage_report_body",
"type": "str",
"title": "Report Body",
"required": false,
"default": ""
},
{
"name": "discord_disk_report_content",
"type": "str",
"title": "Discord Report",
"required": false,
"default": ""
},
{
"name": "inventory_hostname",
"type": "str",
"title": "Inventory Hostname",
"required": false,
"default": ""
},
{
"name": "report_timestamp",
"type": "str",
"title": "Report Timestamp",
"required": false,
"default": ""
},
{
"name": "docker_df_summary",
"type": "str",
"title": "Docker DF Summary",
"required": false,
"default": ""
}
]
}
]
}
@@ -1,51 +0,0 @@
---
- name: Docker healthcheck audit
hosts: all
become: true
gather_facts: false
tasks:
- name: Collect Docker containers
community.docker.docker_host_info:
containers: true
register: docker_host_info
- name: Build list of containers missing healthcheck
ansible.builtin.set_fact:
containers_missing_healthcheck: "{{ (containers_missing_healthcheck | default([])) + [item.Names[0] | default(item.Id)] }}"
when:
- item.Config is defined
- item.Config.Healthcheck is not defined
loop: "{{ docker_host_info.containers | default([]) }}"
- name: Ensure missing-healthcheck list exists
ansible.builtin.set_fact:
containers_missing_healthcheck: []
when: containers_missing_healthcheck is not defined
- name: Build audit report
ansible.builtin.set_fact:
docker_healthcheck_audit_report:
checked_containers: "{{ docker_host_info.containers | default([]) | length }}"
missing_healthcheck_count: "{{ containers_missing_healthcheck | length }}"
missing_healthcheck_containers: "{{ containers_missing_healthcheck }}"
- name: Save healthcheck audit report
ansible.builtin.copy:
dest: "<< audit_report_path | default('/tmp/docker-healthcheck-audit.json') >>"
content: "{{ docker_healthcheck_audit_report | to_nice_json }}"
mode: "0640"
when: audit_save_report | default(true)
- name: Print audit summary
ansible.builtin.debug:
msg: >-
Checked {{ docker_healthcheck_audit_report.checked_containers }} containers,
found {{ docker_healthcheck_audit_report.missing_healthcheck_count }} without healthcheck.
- name: Fail when missing healthchecks are found
ansible.builtin.fail:
msg: "Containers missing healthcheck: {{ containers_missing_healthcheck | join(', ') }}"
when:
- audit_fail_on_missing | default(false)
- containers_missing_healthcheck | length > 0
@@ -1,2 +0,0 @@
collections:
- name: community.docker
@@ -1,86 +0,0 @@
{
"slug": "docker-healthcheck-audit",
"kind": "ansible",
"metadata": {
"name": "Docker Healthcheck Audit",
"description": "Audits Docker containers for missing healthchecks and optionally fails the playbook when violations are found. Can save a JSON audit report to the target host.",
"tags": [
"docker",
"audit",
"healthcheck",
"compliance"
],
"icon": {
"provider": "mdi",
"id": "stethoscope",
"color": "blue"
},
"draft": false,
"version": {
"name": "1",
"source_dep_name": "manual/docker-healthcheck-audit"
}
},
"variables": [
{
"title": "Audit",
"name": "audit",
"items": [
{
"name": "audit_fail_on_missing",
"type": "bool",
"title": "Fail Missing Checks",
"required": false,
"default": false,
"description": "Fail the play if one or more containers are missing a healthcheck"
},
{
"name": "audit_save_report",
"type": "bool",
"title": "Save JSON Report",
"required": false,
"default": true,
"description": "Save audit report to a JSON file on the target host"
},
{
"name": "audit_report_path",
"type": "str",
"title": "Audit Report Path",
"required": false,
"default": "/tmp/docker-healthcheck-audit.json",
"description": "Path to store the generated JSON report",
"config": {
"placeholder": "/tmp/docker-healthcheck-audit.json"
}
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "containers_missing_healthcheck",
"type": "str",
"title": "Containers Missing Healthcheck",
"required": false,
"default": ""
},
{
"name": "docker_healthcheck_audit_report",
"type": "str",
"title": "Healthcheck Audit Report",
"required": false,
"default": ""
},
{
"name": "docker_host_info",
"type": "str",
"title": "Docker Host Info",
"required": false,
"default": ""
}
]
}
]
}
@@ -1,84 +0,0 @@
---
- name: Install Docker on Ubuntu
hosts: all
become: true
gather_facts: true
vars:
docker_apt_arch_map:
x86_64: amd64
aarch64: arm64
armv7l: armhf
ppc64le: ppc64el
s390x: s390x
docker_apt_arch: "{{ docker_apt_arch_map[ansible_architecture] | default('amd64') }}"
tasks:
- name: Remove conflicting packages
ansible.builtin.apt:
name:
- docker.io
- docker-compose
- docker-compose-v2
- docker-doc
- podman-docker
- containerd
- runc
state: absent
- name: Install prerequisite packages
ansible.builtin.apt:
name:
- ca-certificates
- curl
update_cache: true
state: present
- name: Create apt keyrings directory
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
mode: "0755"
- name: Download Docker GPG key
ansible.builtin.get_url:
url: https://download.docker.com/linux/ubuntu/gpg
dest: /etc/apt/keyrings/docker.asc
mode: "0644"
force: true
- name: Add Docker apt repository list file
ansible.builtin.copy:
dest: /etc/apt/sources.list.d/docker.list
mode: "0644"
content: |
deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu {{ ansible_distribution_release }} stable
- name: Install Docker Engine packages
ansible.builtin.apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
update_cache: true
state: present
- name: Ensure Docker service is enabled and started
ansible.builtin.systemd:
name: docker
enabled: true
state: started
<%- if add_user_to_docker == true %>
- name: Add user to docker group
ansible.builtin.user:
name: "<< user >>"
groups: docker
append: true
<%- endif %>
- name: Verify Docker CLI is available
ansible.builtin.command: docker --version
changed_when: false
@@ -1,46 +0,0 @@
{
"slug": "docker-install-ubuntu",
"kind": "ansible",
"metadata": {
"name": "Install Docker on Ubuntu",
"description": "Installs Docker Engine on Ubuntu systems using the official Docker APT repository.",
"tags": [],
"icon": {
"provider": "simple-icons",
"id": "docker",
"color": "blue"
},
"draft": false,
"version": {
"name": "1",
"source_dep_name": "manual/docker-install-ubuntu"
}
},
"variables": [
{
"title": "User Access",
"name": "postinstall",
"items": [
{
"name": "add_user_to_docker",
"type": "bool",
"title": "Docker Group",
"required": false,
"default": false,
"description": "Add a user to the docker group"
},
{
"name": "user",
"type": "str",
"title": "User",
"required": false,
"default": "",
"description": "Username to add to docker group",
"needs": [
"add_user_to_docker=true"
]
}
]
}
]
}
@@ -1,158 +0,0 @@
---
- name: Docker mysql backup
hosts: all
become: true
gather_facts: false
vars:
backup_root: "/opt/docker-db-backups/mysql"
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
backup_file_name: "backup_mysql_<< container_name >>_<< backup_timestamp >>.sql.gz"
backup_file_path: "<< backup_root >>/<< backup_file_name >>"
tasks:
- name: Ensure backup root exists
ansible.builtin.file:
path: "<< backup_root >>"
state: directory
mode: "0750"
- name: Get container information
community.docker.docker_container_info:
name: "<< container_name >>"
register: container_info
- name: Fail if container does not exist
ansible.builtin.fail:
msg: "Container '<< container_name >>' does not exist"
when: not container_info.exists
- name: Run backup and validation
block:
- name: Dump all databases with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e MYSQL_PWD=<< mysql_password | quote >> "<< container_name >>" \
mysqldump --single-transaction --routines --events --triggers \
--all-databases -u "<< mysql_user | default('root') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- mysql_database | default('all') == 'all'
- (mysql_password | default('')) | length > 0
- name: Dump all databases without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
mysqldump --single-transaction --routines --events --triggers \
--all-databases -u "<< mysql_user | default('root') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- mysql_database | default('all') == 'all'
- (mysql_password | default('')) | length == 0
- name: Dump selected database with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e MYSQL_PWD=<< mysql_password | quote >> "<< container_name >>" \
mysqldump --single-transaction --routines --events --triggers \
-u "<< mysql_user | default('root') >>" "<< mysql_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- mysql_database | default('all') != 'all'
- (mysql_password | default('')) | length > 0
- name: Dump selected database without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
mysqldump --single-transaction --routines --events --triggers \
-u "<< mysql_user | default('root') >>" "<< mysql_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- mysql_database | default('all') != 'all'
- (mysql_password | default('')) | length == 0
- name: Validate backup file exists
ansible.builtin.stat:
path: "<< backup_file_path >>"
register: backup_archive_stat
- name: Fail if backup file is missing or empty
ansible.builtin.fail:
msg: "Backup file missing or empty: << backup_file_path >>"
when:
- not (backup_archive_stat.stat.exists | default(false))
or (backup_archive_stat.stat.size | default(0) | int) == 0
- name: Validate gzip archive integrity
ansible.builtin.command: gzip -t "<< backup_file_path >>"
changed_when: false
- name: Validate mysql dump signature
ansible.builtin.shell: |
set -euo pipefail
gzip -dc "<< backup_file_path >>" \
| head -n 50 \
| grep -Eq 'MySQL dump'
args:
executable: /bin/bash
changed_when: false
- name: Remove old mysql backup archives
ansible.builtin.find:
paths: "<< backup_root >>"
patterns: "backup_mysql_<< container_name >>_*.sql.gz"
age: "<< backup_retention_days | default(14) >>d"
recurse: true
register: old_backups
- name: Delete old mysql backup archives
ansible.builtin.file:
path: "<< item.path >>"
state: absent
loop: "<< old_backups.files >>"
- name: Mark backup status success
ansible.builtin.set_fact:
backup_status: "success"
backup_status_message: "Backup and validation succeeded"
rescue:
- name: Mark backup status failed
ansible.builtin.set_fact:
backup_status: "failed"
backup_status_message: "<< ansible_failed_result.msg | default('Backup or validation failed') >>"
always:
- name: Send mysql backup status to Discord
ansible.builtin.uri:
url: "<< discord_webhook | default('') >>"
method: POST
body_format: json
body:
content: |
MySQL Backup << backup_status | default('failed') | upper >>
Host: << inventory_hostname >>
Container: << container_name >>
File: << backup_file_path >>
Message: << backup_status_message | default('n/a') >>
status_code: 204
when:
- send_discord_notification | default(false)
- (discord_webhook | default('')) | length > 0
- name: Fail play if backup failed
ansible.builtin.fail:
msg: "<< backup_status_message | default('Backup failed') >>"
when: backup_status | default('failed') == 'failed'
@@ -1,2 +0,0 @@
collections:
- name: community.docker
@@ -1,186 +0,0 @@
{
"slug": "docker-mysql-backup",
"kind": "ansible",
"metadata": {
"name": "Docker MySQL Backup",
"description": "Connects to a named MySQL container and creates compressed SQL dumps with configurable retention. Supports optional Discord notifications on success or failure.",
"tags": [
"docker",
"backup",
"mysql",
"database"
],
"icon": {
"provider": "simple-icons",
"id": "mysql",
"color": "emerald"
},
"draft": true,
"version": {
"name": "1",
"source_dep_name": "manual/docker-mysql-backup"
}
},
"variables": [
{
"title": "Backup",
"name": "backup",
"items": [
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": true
},
{
"name": "mysql_user",
"type": "str",
"title": "MySQL User",
"required": false,
"default": "root",
"config": {
"placeholder": "root"
}
},
{
"name": "mysql_password",
"type": "str",
"title": "MySQL Password",
"required": false,
"default": ""
},
{
"name": "mysql_database",
"type": "str",
"title": "Database Name",
"required": false,
"default": "all",
"description": "Database to dump, or 'all' for all databases",
"config": {
"placeholder": "all"
}
},
{
"name": "backup_retention_days",
"type": "int",
"title": "Retention Days",
"required": false,
"default": 14,
"description": "Delete backup archives older than this many days",
"config": {
"slider": true,
"min": 1,
"max": 365,
"step": 1,
"placeholder": "14",
"unit": "days"
}
}
]
},
{
"title": "Notification",
"name": "notification",
"items": [
{
"name": "send_discord_notification",
"type": "bool",
"title": "Send Discord Notification",
"required": false,
"default": false,
"description": "Send success/failure status to Discord webhook"
},
{
"name": "discord_webhook",
"type": "secret",
"title": "Discord Webhook",
"required": false,
"default": "",
"description": "Discord webhook URL for notifications"
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "backup_file_name",
"type": "str",
"title": "Backup File Name",
"required": false,
"default": ""
},
{
"name": "backup_root",
"type": "str",
"title": "Backup Root",
"required": false,
"default": "/backups/mysql",
"config": {
"placeholder": "/backups/mysql"
}
},
{
"name": "ansible_failed_result",
"type": "str",
"title": "Failed Result",
"required": false,
"default": ""
},
{
"name": "backup_timestamp",
"type": "str",
"title": "Backup Timestamp",
"required": false,
"default": ""
},
{
"name": "old_backups",
"type": "str",
"title": "Old Backups",
"required": false,
"default": ""
},
{
"name": "backup_status",
"type": "str",
"title": "Backup Status",
"required": false,
"default": ""
},
{
"name": "inventory_hostname",
"type": "str",
"title": "Inventory Hostname",
"required": false,
"default": ""
},
{
"name": "backup_file_path",
"type": "str",
"title": "Backup File Path",
"required": false,
"default": ""
},
{
"name": "item",
"type": "str",
"title": "Loop Item",
"required": false,
"default": ""
},
{
"name": "backup_status_message",
"type": "str",
"title": "Backup Status Message",
"required": false,
"default": "",
"config": {
"textarea": true
}
}
]
}
]
}
@@ -1,93 +0,0 @@
---
- name: Docker orphan detection
hosts: all
become: true
gather_facts: false
vars:
report_root: "/opt/docker-reports"
report_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
report_file_name: "docker_orphan_detection_<< inventory_hostname >>_<< report_timestamp >>.txt"
tasks:
- name: Ensure report root exists
ansible.builtin.file:
path: "<< report_root >>"
state: directory
mode: "0750"
- name: List exited containers
ansible.builtin.command: docker ps -a --filter status=exited --format "{{.Names}}"
register: orphan_exited_containers
changed_when: false
- name: List dead containers
ansible.builtin.command: docker ps -a --filter status=dead --format "{{.Names}}"
register: orphan_dead_containers
changed_when: false
- name: List dangling images
ansible.builtin.command: docker images -f dangling=true -q
register: orphan_dangling_images
changed_when: false
- name: List dangling volumes
ansible.builtin.command: docker volume ls -qf dangling=true
register: orphan_dangling_volumes
changed_when: false
- name: List dangling networks
ansible.builtin.command: docker network ls -qf dangling=true
register: orphan_dangling_networks
changed_when: false
- name: Build orphan report body
ansible.builtin.set_fact:
docker_orphan_report_body: |
Host: << inventory_hostname >>
Timestamp: {{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}
Exited containers (<< orphan_exited_containers.stdout_lines | length >>):
<< (orphan_exited_containers.stdout_lines | default([])) | join('\n') if (orphan_exited_containers.stdout_lines | length > 0) else 'none' >>
Dead containers (<< orphan_dead_containers.stdout_lines | length >>):
<< (orphan_dead_containers.stdout_lines | default([])) | join('\n') if (orphan_dead_containers.stdout_lines | length > 0) else 'none' >>
Dangling images (<< orphan_dangling_images.stdout_lines | length >>):
<< (orphan_dangling_images.stdout_lines | default([])) | join('\n') if (orphan_dangling_images.stdout_lines | length > 0) else 'none' >>
Dangling volumes (<< orphan_dangling_volumes.stdout_lines | length >>):
<< (orphan_dangling_volumes.stdout_lines | default([])) | join('\n') if (orphan_dangling_volumes.stdout_lines | length > 0) else 'none' >>
Dangling networks (<< orphan_dangling_networks.stdout_lines | length >>):
<< (orphan_dangling_networks.stdout_lines | default([])) | join('\n') if (orphan_dangling_networks.stdout_lines | length > 0) else 'none' >>
- name: Save local orphan detection report
ansible.builtin.copy:
dest: "<< report_root >>/<< report_file_name >>"
content: "<< docker_orphan_report_body >>"
mode: "0640"
- name: Build Discord report content
ansible.builtin.set_fact:
discord_orphan_report_content: |
<< discord_message_prefix | default('Docker Orphan Detection Report') >>
```text
<< docker_orphan_report_body | truncate(1700, true, '...') >>
```
when:
- discord_enabled | default(false)
- (discord_webhook_url | default('')) | length > 0
- name: Send orphan report to Discord webhook
ansible.builtin.uri:
url: "<< discord_webhook_url | default('') >>"
method: POST
body_format: json
body:
username: "<< discord_username | default('Docker Reporter') >>"
content: "<< discord_orphan_report_content >>"
status_code: 204
when:
- discord_enabled | default(false)
- (discord_webhook_url | default('')) | length > 0
@@ -1,155 +0,0 @@
{
"slug": "docker-orphan-detection",
"kind": "ansible",
"metadata": {
"name": "Docker Orphan Detection",
"description": "Detects orphaned Docker resources (dead/exited containers, dangling images, volumes, and networks) and optionally sends a report to Discord via webhook.",
"tags": [
"docker",
"audit",
"orphan",
"report"
],
"icon": {
"provider": "mdi",
"id": "magnify",
"color": "fuchsia"
},
"draft": true,
"version": {
"name": "1",
"source_dep_name": "manual/docker-orphan-detection"
}
},
"variables": [
{
"title": "Discord Webhook",
"name": "discord",
"items": [
{
"name": "discord_enabled",
"type": "bool",
"title": "Discord",
"required": false,
"default": false,
"description": "Send orphan detection summary to Discord webhook"
},
{
"name": "discord_webhook_url",
"type": "str",
"title": "Discord Webhook URL",
"required": false,
"default": ""
},
{
"name": "discord_username",
"type": "str",
"title": "Discord Username",
"required": false,
"default": "Docker Reporter",
"description": "Sender name for webhook message",
"config": {
"placeholder": "Docker Reporter"
}
},
{
"name": "discord_message_prefix",
"type": "str",
"title": "Discord Message Prefix",
"required": false,
"default": "Docker Orphan Detection Report",
"description": "Prefix line sent before report content",
"config": {
"placeholder": "Docker Orphan Detection Report"
}
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "report_root",
"type": "str",
"title": "Report Root",
"required": false,
"default": "/reports",
"config": {
"placeholder": "/reports"
}
},
{
"name": "orphan_dead_containers",
"type": "str",
"title": "Orphan Dead Containers",
"required": false,
"default": ""
},
{
"name": "orphan_dangling_images",
"type": "str",
"title": "Orphan Dangling Images",
"required": false,
"default": ""
},
{
"name": "orphan_dangling_volumes",
"type": "str",
"title": "Orphan Dangling Volumes",
"required": false,
"default": ""
},
{
"name": "docker_orphan_report_body",
"type": "str",
"title": "Orphan Report Body",
"required": false,
"default": ""
},
{
"name": "report_timestamp",
"type": "str",
"title": "Report Timestamp",
"required": false,
"default": ""
},
{
"name": "orphan_exited_containers",
"type": "str",
"title": "Orphan Exited Containers",
"required": false,
"default": ""
},
{
"name": "orphan_dangling_networks",
"type": "str",
"title": "Orphan Dangling Networks",
"required": false,
"default": ""
},
{
"name": "report_file_name",
"type": "str",
"title": "Report File Name",
"required": false,
"default": ""
},
{
"name": "discord_orphan_report_content",
"type": "str",
"title": "Orphan Report",
"required": false,
"default": ""
},
{
"name": "inventory_hostname",
"type": "str",
"title": "Inventory Hostname",
"required": false,
"default": ""
}
]
}
]
}
@@ -1,156 +0,0 @@
---
- name: Docker postgres backup
hosts: all
become: true
gather_facts: false
vars:
backup_root: "/opt/docker-db-backups/postgres"
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
backup_file_name: "backup_postgres_<< container_name >>_<< backup_timestamp >>.sql.gz"
backup_file_path: "<< backup_root >>/<< backup_file_name >>"
tasks:
- name: Ensure backup root exists
ansible.builtin.file:
path: "<< backup_root >>"
state: directory
mode: "0750"
- name: Get container information
community.docker.docker_container_info:
name: "<< container_name >>"
register: container_info
- name: Fail if container does not exist
ansible.builtin.fail:
msg: "Container '<< container_name >>' does not exist"
when: not container_info.exists
- name: Run backup and validation
block:
- name: Dump all databases with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') == 'all'
- (postgres_password | default('')) | length > 0
- name: Dump all databases without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
pg_dumpall -U "<< postgres_user | default('postgres') >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') == 'all'
- (postgres_password | default('')) | length == 0
- name: Dump selected database with password
ansible.builtin.shell: |
set -euo pipefail
docker exec -e PGPASSWORD=<< postgres_password | quote >> "<< container_name >>" \
pg_dump -U "<< postgres_user | default('postgres') >>" \
"<< postgres_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') != 'all'
- (postgres_password | default('')) | length > 0
- name: Dump selected database without password
ansible.builtin.shell: |
set -euo pipefail
docker exec "<< container_name >>" \
pg_dump -U "<< postgres_user | default('postgres') >>" \
"<< postgres_database >>" \
| gzip > "<< backup_file_path >>"
args:
executable: /bin/bash
changed_when: true
when:
- postgres_database | default('all') != 'all'
- (postgres_password | default('')) | length == 0
- name: Validate backup file exists
ansible.builtin.stat:
path: "<< backup_file_path >>"
register: backup_archive_stat
- name: Fail if backup file is missing or empty
ansible.builtin.fail:
msg: "Backup file missing or empty: << backup_file_path >>"
when:
- not (backup_archive_stat.stat.exists | default(false))
or (backup_archive_stat.stat.size | default(0) | int) == 0
- name: Validate gzip archive integrity
ansible.builtin.command: gzip -t "<< backup_file_path >>"
changed_when: false
- name: Validate postgres dump signature
ansible.builtin.shell: |
set -euo pipefail
gzip -dc "<< backup_file_path >>" \
| head -n 50 \
| grep -Eq 'PostgreSQL database dump'
args:
executable: /bin/bash
changed_when: false
- name: Remove old postgres backup archives
ansible.builtin.find:
paths: "<< backup_root >>"
patterns: "backup_postgres_<< container_name >>_*.sql.gz"
age: "<< backup_retention_days | default(14) >>d"
recurse: true
register: old_backups
- name: Delete old postgres backup archives
ansible.builtin.file:
path: "<< item.path >>"
state: absent
loop: "<< old_backups.files >>"
- name: Mark backup status success
ansible.builtin.set_fact:
backup_status: "success"
backup_status_message: "Backup and validation succeeded"
rescue:
- name: Mark backup status failed
ansible.builtin.set_fact:
backup_status: "failed"
backup_status_message: "<< ansible_failed_result.msg | default('Backup or validation failed') >>"
always:
- name: Send postgres backup status to Discord
ansible.builtin.uri:
url: "<< discord_webhook | default('') >>"
method: POST
body_format: json
body:
content: |
PostgreSQL Backup << backup_status | default('failed') | upper >>
Host: << inventory_hostname >>
Container: << container_name >>
File: << backup_file_path >>
Message: << backup_status_message | default('n/a') >>
status_code: 204
when:
- send_discord_notification | default(false)
- (discord_webhook | default('')) | length > 0
- name: Fail play if backup failed
ansible.builtin.fail:
msg: "<< backup_status_message | default('Backup failed') >>"
when: backup_status | default('failed') == 'failed'
@@ -1,2 +0,0 @@
collections:
- name: community.docker
@@ -1,187 +0,0 @@
{
"slug": "docker-postgres-backup",
"kind": "ansible",
"metadata": {
"name": "Docker Postgres Backup",
"description": "Connects to a named PostgreSQL container and creates compressed SQL dumps with configurable retention. Supports optional Discord notifications on success or failure.",
"tags": [
"docker",
"backup",
"postgres",
"database"
],
"icon": {
"provider": "simple-icons",
"id": "postgresql",
"color": "emerald"
},
"draft": true,
"version": {
"name": "1",
"source_dep_name": "manual/docker-postgres-backup"
}
},
"variables": [
{
"title": "Backup",
"name": "backup",
"items": [
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": true
},
{
"name": "postgres_user",
"type": "str",
"title": "Postgres User",
"required": false,
"default": "postgres",
"description": "Database user for pg_dump/pg_dumpall",
"config": {
"placeholder": "postgres"
}
},
{
"name": "postgres_password",
"type": "str",
"title": "Postgres Password",
"required": false,
"default": ""
},
{
"name": "postgres_database",
"type": "str",
"title": "Database Name",
"required": false,
"default": "all",
"description": "Database to dump, or 'all' for all databases",
"config": {
"placeholder": "all"
}
},
{
"name": "backup_retention_days",
"type": "int",
"title": "Retention Days",
"required": false,
"default": 14,
"description": "Delete backup archives older than this many days",
"config": {
"slider": true,
"min": 1,
"max": 365,
"step": 1,
"placeholder": "14",
"unit": "days"
}
}
]
},
{
"title": "Notification",
"name": "notification",
"items": [
{
"name": "send_discord_notification",
"type": "bool",
"title": "Send Discord Notification",
"required": false,
"default": false,
"description": "Send success/failure status to Discord webhook"
},
{
"name": "discord_webhook",
"type": "secret",
"title": "Discord Webhook",
"required": false,
"default": "",
"description": "Discord webhook URL for notifications"
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "backup_status",
"type": "str",
"title": "Backup Status",
"required": false,
"default": ""
},
{
"name": "inventory_hostname",
"type": "str",
"title": "Inventory Hostname",
"required": false,
"default": ""
},
{
"name": "backup_timestamp",
"type": "str",
"title": "Backup Timestamp",
"required": false,
"default": ""
},
{
"name": "item",
"type": "str",
"title": "Loop Item",
"required": false,
"default": ""
},
{
"name": "backup_file_path",
"type": "str",
"title": "Backup File Path",
"required": false,
"default": ""
},
{
"name": "old_backups",
"type": "str",
"title": "Old Backups",
"required": false,
"default": ""
},
{
"name": "backup_root",
"type": "str",
"title": "Backup Root",
"required": false,
"default": "/backups/postgres",
"config": {
"placeholder": "/backups/postgres"
}
},
{
"name": "backup_file_name",
"type": "str",
"title": "Backup File Name",
"required": false,
"default": ""
},
{
"name": "ansible_failed_result",
"type": "str",
"title": "Failed Result",
"required": false,
"default": ""
},
{
"name": "backup_status_message",
"type": "str",
"title": "Backup Status Message",
"required": false,
"default": "",
"config": {
"textarea": true
}
}
]
}
]
}
@@ -1,77 +0,0 @@
---
- name: Docker volume backup
hosts: all
become: true
gather_facts: false
vars:
backup_root: "/opt/docker-volume-backups"
backup_timestamp: "{{ lookup('pipe', 'date +%Y%m%d-%H%M%S') }}"
backup_archive_name: "backup_<< container_name >>_{{ backup_timestamp }}.tar.gz"
tasks:
- name: Ensure backup root exists
ansible.builtin.file:
path: "{{ backup_root }}"
state: directory
mode: "0750"
- name: Get container information
community.docker.docker_container_info:
name: "<< container_name >>"
register: container_info
- name: Fail if container does not exist
ansible.builtin.fail:
msg: "Container '<< container_name >>' does not exist"
when: not container_info.exists
- name: Record container running state
ansible.builtin.set_fact:
container_was_running: "{{ (container_info.container.State.Status | default('')) == 'running' }}"
- name: Stop container for consistent backup
community.docker.docker_container:
name: "<< container_name >>"
state: stopped
when:
- stop_containers_during_backup | default(false)
- container_was_running
- name: Back up container path using all attached volumes
ansible.builtin.shell: |
set -eu
docker run --rm \
--volumes-from "<< container_name >>" \
-v "{{ backup_root }}":/backup \
busybox sh -c '
test -e "<< backup_container_path >>"
tar -czf "/backup/{{ backup_archive_name }}" \
-C / "<< backup_container_path | regex_replace('^/', '') >>"
'
args:
executable: /bin/sh
changed_when: true
- name: Start container after backup
community.docker.docker_container:
name: "<< container_name >>"
state: started
when:
- stop_containers_during_backup | default(false)
- container_was_running
- name: Remove old backup archives
ansible.builtin.find:
paths: "{{ backup_root }}"
patterns: "backup_<< container_name >>_*.tar.gz"
age: "<< backup_retention_days | default(14) >>d"
recurse: true
register: old_backups
- name: Delete old backup archives
ansible.builtin.file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_backups.files }}"
@@ -1,2 +0,0 @@
collections:
- name: community.docker
@@ -1,113 +0,0 @@
{
"slug": "docker-volume-backup",
"kind": "ansible",
"metadata": {
"name": "Docker Volume Backup",
"description": "Backs up data from a Docker container path by auto-attaching all container volumes into a temporary backup container. Creates timestamped tar.gz archives with configurable retention.",
"tags": [
"docker",
"backup",
"volume",
"container"
],
"icon": {
"provider": "mdi",
"id": "harddisk",
"color": "emerald"
},
"draft": true,
"version": {
"name": "1",
"source_dep_name": "manual/docker-volume-backup"
}
},
"variables": [
{
"title": "Backup",
"name": "backup",
"items": [
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": true
},
{
"name": "backup_retention_days",
"type": "int",
"title": "Retention Days",
"required": false,
"default": 14,
"description": "Delete backup archives older than this many days",
"config": {
"slider": true,
"min": 1,
"max": 365,
"step": 1,
"placeholder": "14",
"unit": "days"
}
},
{
"name": "backup_container_path",
"type": "str",
"title": "Backup Path",
"required": true,
"description": "Path inside the container that should be archived"
},
{
"name": "stop_containers_during_backup",
"type": "bool",
"title": "Stop Backup",
"required": false,
"default": false,
"description": "If enabled, stop the container before backup and start it again afterwards if it was running"
}
]
},
{
"title": "Internal",
"name": "internal",
"items": [
{
"name": "backup_timestamp",
"type": "str",
"title": "Backup Timestamp",
"required": false,
"default": ""
},
{
"name": "backup_root",
"type": "str",
"title": "Backup Root",
"required": false,
"default": "/backups",
"config": {
"placeholder": "/backups"
}
},
{
"name": "backup_archive_name",
"type": "str",
"title": "Backup Archive Name",
"required": false,
"default": ""
},
{
"name": "item",
"type": "str",
"title": "Loop Item",
"required": false,
"default": ""
},
{
"name": "old_backups",
"type": "str",
"title": "Old Backups",
"required": false,
"default": ""
}
]
}
]
}
@@ -1,404 +0,0 @@
---
- name: Configure K3s post-install platform services - Fetch kubeconfig
hosts: all
gather_facts: false
become: true
<%- if secrets_file %>
vars_files:
- << secrets_file >>
<%- endif %>
vars:
kubeconfig_path: "<< kubeconfig_local_path >>"
kubeconfig_server_target: "<< kubeconfig_server_endpoint >>"
tasks:
- name: Validate that the kubeconfig source host exists in inventory
ansible.builtin.assert:
that:
- "'<< kubeconfig_source_host | default('k3s-server-01') >>' in hostvars"
fail_msg: "Inventory host '<< kubeconfig_source_host | default('k3s-server-01') >>' was not found."
run_once: true
- name: Read kubeconfig from the primary K3s server
ansible.builtin.slurp:
src: "<< kubeconfig_source_path | default('/etc/rancher/k3s/k3s.yaml') >>"
delegate_to: "<< kubeconfig_source_host | default('k3s-server-01') >>"
register: k3s_source_kubeconfig
when: fetch_kubeconfig | default(true)
run_once: true
- name: Ensure local kubeconfig directory exists
ansible.builtin.file:
path: "{{ kubeconfig_path | dirname }}"
state: directory
mode: "0700"
delegate_to: localhost
run_once: true
when: fetch_kubeconfig | default(true)
- name: Write kubeconfig to the control machine
ansible.builtin.copy:
dest: "{{ kubeconfig_path }}"
mode: "0600"
content: "{{ (k3s_source_kubeconfig.content | b64decode) | regex_replace('https://127\\.0\\.0\\.1:6443', 'https://' ~ kubeconfig_server_target ~ ':6443') }}"
delegate_to: localhost
run_once: true
when: fetch_kubeconfig | default(true)
- name: Configure K3s post-install platform services - Prepare Longhorn prerequisites
hosts: all
gather_facts: false
become: true
<%- if secrets_file %>
vars_files:
- << secrets_file >>
<%- endif %>
vars:
longhorn_target_hosts: "{{ query('inventory_hostnames', '<< longhorn_node_hosts >>') }}"
tasks:
- name: Validate Longhorn target host pattern
ansible.builtin.assert:
that:
- longhorn_target_hosts | length > 0
fail_msg: "No inventory hosts matched '<< longhorn_node_hosts >>'."
when: longhorn_prerequisites_enabled | default(false)
run_once: true
- name: Install Longhorn prerequisite packages
ansible.builtin.apt:
name:
- open-iscsi
- nfs-common
- cryptsetup
- util-linux
state: present
update_cache: true
when:
- longhorn_prerequisites_enabled | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Ensure iscsid service is enabled and running
ansible.builtin.service:
name: iscsid
state: started
enabled: true
when:
- longhorn_prerequisites_enabled | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Ensure open-iscsi service is enabled and running when present
ansible.builtin.service:
name: open-iscsi
state: started
enabled: true
failed_when: false
when:
- longhorn_prerequisites_enabled | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Persist dm_crypt kernel module for Longhorn
ansible.builtin.copy:
dest: /etc/modules-load.d/longhorn.conf
mode: "0644"
content: |
dm_crypt
when:
- longhorn_prerequisites_enabled | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Load dm_crypt kernel module immediately
community.general.modprobe:
name: dm_crypt
state: present
when:
- longhorn_prerequisites_enabled | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Disable multipathd when the environment does not use multipath storage
ansible.builtin.service:
name: multipathd
state: stopped
enabled: false
failed_when: false
when:
- longhorn_prerequisites_enabled | default(false)
- longhorn_disable_multipathd | default(false)
- inventory_hostname in longhorn_target_hosts
- name: Configure K3s post-install platform services - Configure cluster add-ons
hosts: all
gather_facts: false
<%- if secrets_file %>
vars_files:
- << secrets_file >>
<%- endif %>
vars:
kubeconfig_path: "<< kubeconfig_local_path >>"
tasks:
- block:
- name: Ensure the kubeconfig file exists before applying cluster resources
ansible.builtin.stat:
path: "{{ kubeconfig_path }}"
register: kubeconfig_stat
- name: Fail when the kubeconfig file is missing
ansible.builtin.fail:
msg: "Kubeconfig not found at {{ kubeconfig_path }}. Enable fetch_kubeconfig or provide an existing kubeconfig path."
when: not (kubeconfig_stat.stat.exists | default(false))
- name: Verify Helm is available on the control machine when required
ansible.builtin.command:
cmd: helm version --short
changed_when: false
when: (cert_manager_install_enabled | default(false)) or (metrics_server_install_enabled | default(false)) or (longhorn_install_enabled | default(false))
- name: Validate ACME email when ClusterIssuer creation is enabled
ansible.builtin.assert:
that:
- (cert_manager_acme_email | default('')) | length > 0
fail_msg: "cert_manager_acme_email must be set when cert_manager_clusterissuer_enabled=true."
when: cert_manager_clusterissuer_enabled | default(false)
- name: Build namespace list for enabled platform components
ansible.builtin.set_fact:
managed_namespaces: >-
{{
(
(extra_namespaces_yaml | default('[]') | from_yaml)
+ ([cert_manager_namespace | default('cert-manager')] if cert_manager_install_enabled | default(false) or cert_manager_create_cloudflare_secret | default(false) or cert_manager_clusterissuer_enabled | default(false) else [])
+ ([longhorn_namespace | default('longhorn-system')] if longhorn_install_enabled | default(false) else [])
+ ([traefik_dashboard_namespace | default('traefik')] if traefik_dashboard_exposure_enabled | default(false) else [])
) | unique
}}
when: ensure_namespaces | default(true)
- name: Ensure selected namespaces exist
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: v1
kind: Namespace
metadata:
name: "{{ item }}"
loop: "{{ managed_namespaces | default([]) }}"
loop_control:
label: "{{ item }}"
when:
- ensure_namespaces | default(true)
- item | length > 0
- name: Add cert-manager Helm repository
kubernetes.core.helm_repository:
name: jetstack
repo_url: https://charts.jetstack.io
when: cert_manager_install_enabled | default(false)
- name: Install cert-manager
kubernetes.core.helm:
kubeconfig: "{{ kubeconfig_path }}"
name: cert-manager
chart_ref: jetstack/cert-manager
chart_version: "<< cert_manager_chart_version | default('1.18.2') >>"
release_namespace: "<< cert_manager_namespace | default('cert-manager') >>"
create_namespace: true
wait: true
values:
crds:
enabled: true
when: cert_manager_install_enabled | default(false)
- name: Create Cloudflare API token secret placeholder for cert-manager
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: v1
kind: Secret
metadata:
name: "<< cert_manager_cloudflare_secret_name | default('cloudflare-api-token-secret') >>"
namespace: "<< cert_manager_namespace | default('cert-manager') >>"
type: Opaque
stringData:
<< cert_manager_cloudflare_secret_key | default('api-token') >>: "<< cert_manager_cloudflare_api_token | default('replace-me-with-your-cloudflare-api-token') >>"
when: cert_manager_create_cloudflare_secret | default(false)
- name: Create cert-manager ClusterIssuer
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: "<< cert_manager_clusterissuer_name | default('letsencrypt-production') >>"
spec:
acme:
email: "<< cert_manager_acme_email >>"
server: "<< cert_manager_acme_server | default('https://acme-v02.api.letsencrypt.org/directory') >>"
privateKeySecretRef:
name: "<< cert_manager_clusterissuer_name | default('letsencrypt-production') >>-account-key"
solvers:
- dns01:
cloudflare:
apiTokenSecretRef:
name: "<< cert_manager_cloudflare_secret_name | default('cloudflare-api-token-secret') >>"
key: "<< cert_manager_cloudflare_secret_key | default('api-token') >>"
when: cert_manager_clusterissuer_enabled | default(false)
- name: Add metrics-server Helm repository
kubernetes.core.helm_repository:
name: metrics-server
repo_url: https://kubernetes-sigs.github.io/metrics-server/
when: metrics_server_install_enabled | default(false)
- name: Install metrics-server
kubernetes.core.helm:
kubeconfig: "{{ kubeconfig_path }}"
name: metrics-server
chart_ref: metrics-server/metrics-server
chart_version: "<< metrics_server_chart_version | default('3.13.0') >>"
release_namespace: "<< metrics_server_namespace | default('kube-system') >>"
create_namespace: true
wait: true
<%- if metrics_server_kubelet_insecure_tls %>
values:
args:
- --kubelet-insecure-tls
<%- endif %>
when: metrics_server_install_enabled | default(false)
- name: Add Longhorn Helm repository
kubernetes.core.helm_repository:
name: longhorn
repo_url: https://charts.longhorn.io
when: longhorn_install_enabled | default(false)
- name: Install Longhorn
kubernetes.core.helm:
kubeconfig: "{{ kubeconfig_path }}"
name: longhorn
chart_ref: longhorn/longhorn
chart_version: "<< longhorn_chart_version | default('1.9.1') >>"
release_namespace: "<< longhorn_namespace | default('longhorn-system') >>"
create_namespace: true
wait: true
values:
defaultSettings:
defaultDataPath: "<< longhorn_default_data_path | default('/var/lib/longhorn') >>"
when: longhorn_install_enabled | default(false)
- name: Expose Traefik dashboard through an IngressRoute
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
definition:
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: "<< traefik_dashboard_namespace | default('traefik') >>"
spec:
entryPoints:
- "<< traefik_dashboard_entrypoint | default('websecure') >>"
routes:
- match: "Host(`<< traefik_dashboard_host | default('traefik.example.internal') >>`)"
kind: Rule
<%- if traefik_dashboard_middleware_name %>
middlewares:
- name: "<< traefik_dashboard_middleware_name >>"
namespace: "<< traefik_dashboard_middleware_namespace | default('traefik') >>"
<%- endif %>
services:
- kind: TraefikService
name: api@internal
<%- if traefik_dashboard_tls_enabled %>
tls:
secretName: "<< traefik_dashboard_tls_secret_name | default('traefik-dashboard-tls') >>"
<%- endif %>
when: traefik_dashboard_exposure_enabled | default(false)
- name: Read kube-proxy ConfigMap before strictARP patch
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig_path }}"
api_version: v1
kind: ConfigMap
namespace: kube-system
name: kube-proxy
register: kube_proxy_configmap
when: kube_proxy_strict_arp_enabled | default(false)
- name: Build kube-proxy configuration with strictARP enabled
ansible.builtin.set_fact:
kube_proxy_rendered_config: >-
{{
(kube_proxy_configmap.resources[0].data['config.conf'] | from_yaml)
| combine(
{
'ipvs': (
(kube_proxy_configmap.resources[0].data['config.conf'] | from_yaml).ipvs
| default({})
| combine({'strictARP': true})
)
},
recursive=True
)
}}
when:
- kube_proxy_strict_arp_enabled | default(false)
- kube_proxy_configmap.resources | length > 0
- name: Patch kube-proxy ConfigMap with strictARP enabled
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
merge_type:
- merge
definition:
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-proxy
namespace: kube-system
data:
config.conf: "{{ kube_proxy_rendered_config | to_nice_yaml(indent=2) }}"
when:
- kube_proxy_strict_arp_enabled | default(false)
- kube_proxy_configmap.resources | length > 0
- name: Read kube-proxy daemonset for restart
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig_path }}"
api_version: apps/v1
kind: DaemonSet
namespace: kube-system
name: kube-proxy
register: kube_proxy_daemonset
when: kube_proxy_strict_arp_enabled | default(false)
- name: Restart kube-proxy daemonset after strictARP change
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig_path }}"
state: present
merge_type:
- merge
definition:
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-proxy
namespace: kube-system
spec:
template:
metadata:
annotations:
homelab.boilerplates.io/restarted-at: "{{ lookup('pipe', 'date -u +%Y-%m-%dT%H:%M:%SZ') }}"
when:
- kube_proxy_strict_arp_enabled | default(false)
- kube_proxy_daemonset.resources | length > 0
delegate_to: localhost
run_once: true
@@ -1,527 +0,0 @@
{
"slug": "k3s-postinstall",
"kind": "ansible",
"metadata": {
"name": "K3s Post-Install Platform Setup",
"description": "Configures common post-install building blocks for a fresh K3s cluster. Supports kubeconfig retrieval, namespace management, Traefik dashboard exposure, cert-manager bootstrap, metrics-server, Longhorn preparation, and optional kube-system tuning.",
"tags": [
"kubernetes",
"k3s",
"postinstall",
"helm",
"homelab"
],
"icon": {
"provider": "simple-icons",
"id": "kubernetes"
},
"draft": false,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/k3s-postinstall"
}
},
"variables": [
{
"title": "Execution",
"name": "execution",
"items": [
{
"name": "secrets_file",
"type": "str",
"title": "Secrets File",
"required": false,
"default": "",
"description": "Optional vars file to load before running the playbook."
}
]
},
{
"title": "Kubeconfig",
"name": "kubeconfig",
"items": [
{
"name": "fetch_kubeconfig",
"type": "bool",
"title": "Fetch Kubeconfig",
"required": false,
"default": true,
"description": "Fetch the admin kubeconfig from the first K3s server before applying cluster resources"
},
{
"name": "kubeconfig_source_host",
"type": "str",
"title": "Source Host",
"required": false,
"default": "k3s-server-01",
"description": "Inventory host that stores the K3s admin kubeconfig",
"needs": [
"fetch_kubeconfig=true"
],
"config": {
"placeholder": "k3s-server-01"
}
},
{
"name": "kubeconfig_source_path",
"type": "str",
"title": "Source Path",
"required": false,
"default": "/etc/rancher/k3s/k3s.yaml",
"description": "Path to the kubeconfig file on the K3s server",
"needs": [
"fetch_kubeconfig=true"
],
"config": {
"placeholder": "/etc/rancher/k3s/k3s.yaml"
}
},
{
"name": "kubeconfig_server_endpoint",
"type": "str",
"title": "API Endpoint",
"required": false,
"default": "{{ hostvars['k3s-server-01'].ansible_host | default('k3s-server-01') }}",
"description": "Host or IP that should replace 127.0.0.1 in the fetched kubeconfig",
"needs": [
"fetch_kubeconfig=true"
],
"config": {
"placeholder": "192.168.1.10"
}
},
{
"name": "kubeconfig_local_path",
"type": "str",
"title": "Local Kubeconfig",
"required": false,
"default": "{{ lookup('env', 'HOME') }}/.kube/k3s.yaml",
"description": "Local kubeconfig path used by the control machine for all cluster operations",
"config": {
"placeholder": "{{ lookup('env', 'HOME') }}/.kube/k3s.yaml"
}
}
]
},
{
"title": "Namespaces",
"name": "namespaces",
"items": [
{
"name": "ensure_namespaces",
"type": "bool",
"title": "Manage Namespaces",
"required": false,
"default": true,
"description": "Ensure common namespaces exist before installing platform add-ons"
},
{
"name": "extra_namespaces_yaml",
"type": "str",
"title": "Extra Namespaces",
"required": false,
"default": "[]",
"description": "YAML list of extra namespaces to create",
"needs": [
"ensure_namespaces=true"
],
"config": {
"textarea": true,
"placeholder": "- apps\n- monitoring"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"items": [
{
"name": "traefik_dashboard_exposure_enabled",
"type": "bool",
"title": "Expose Dashboard",
"required": false,
"default": false,
"description": "Create a Traefik IngressRoute that exposes the internal Traefik dashboard"
},
{
"name": "traefik_dashboard_namespace",
"type": "str",
"title": "Namespace",
"required": false,
"default": "traefik",
"description": "Namespace where the Traefik release is installed",
"needs": [
"traefik_dashboard_exposure_enabled=true"
],
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_dashboard_host",
"type": "str",
"title": "Dashboard Host",
"required": false,
"default": "traefik.example.internal",
"description": "FQDN used to expose the Traefik dashboard",
"needs": [
"traefik_dashboard_exposure_enabled=true"
],
"config": {
"placeholder": "traefik.example.internal"
}
},
{
"name": "traefik_dashboard_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": false,
"default": "websecure",
"description": "Traefik entrypoint for the dashboard route",
"needs": [
"traefik_dashboard_exposure_enabled=true"
],
"config": {
"placeholder": "websecure"
}
},
{
"name": "traefik_dashboard_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": true,
"needs": [
"traefik_dashboard_exposure_enabled=true"
]
},
{
"name": "traefik_dashboard_tls_secret_name",
"type": "str",
"title": "TLS Secret",
"required": false,
"default": "traefik-dashboard-tls",
"description": "TLS secret used by the dashboard route when TLS is enabled",
"needs": [
"traefik_dashboard_exposure_enabled=true",
"traefik_dashboard_tls_enabled=true"
],
"config": {
"placeholder": "traefik-dashboard-tls"
}
},
{
"name": "traefik_dashboard_middleware_name",
"type": "str",
"title": "Middleware",
"required": false,
"default": "",
"description": "Optional Traefik middleware name attached to the dashboard route",
"needs": [
"traefik_dashboard_exposure_enabled=true"
],
"config": {
"placeholder": "traefik-dashboard-auth"
}
},
{
"name": "traefik_dashboard_middleware_namespace",
"type": "str",
"title": "Middleware NS",
"required": false,
"default": "traefik",
"needs": [
"traefik_dashboard_exposure_enabled=true"
],
"config": {
"placeholder": "traefik"
}
}
]
},
{
"title": "Cert-Manager",
"name": "cert-manager",
"items": [
{
"name": "cert_manager_install_enabled",
"type": "bool",
"title": "Install cert-manager",
"required": false,
"default": false
},
{
"name": "cert_manager_chart_version",
"type": "str",
"title": "Chart Version",
"required": false,
"default": "1.18.2",
"needs": [
"cert_manager_install_enabled=true"
],
"config": {
"placeholder": "1.18.2"
}
},
{
"name": "cert_manager_namespace",
"type": "str",
"title": "Namespace",
"required": false,
"default": "cert-manager",
"config": {
"placeholder": "cert-manager"
}
},
{
"name": "cert_manager_create_cloudflare_secret",
"type": "bool",
"title": "Cloudflare Secret",
"required": false,
"default": false,
"description": "Create a placeholder secret for a Cloudflare DNS API token"
},
{
"name": "cert_manager_cloudflare_secret_name",
"type": "str",
"title": "Secret Name",
"required": false,
"default": "cloudflare-api-token-secret",
"description": "Secret name for the Cloudflare DNS API token",
"needs": [
"cert_manager_create_cloudflare_secret=true"
],
"config": {
"placeholder": "cloudflare-api-token-secret"
}
},
{
"name": "cert_manager_cloudflare_secret_key",
"type": "str",
"title": "Secret Key",
"required": false,
"default": "api-token",
"description": "Secret key that stores the Cloudflare API token",
"needs": [
"cert_manager_create_cloudflare_secret=true"
],
"config": {
"placeholder": "api-token"
}
},
{
"name": "cert_manager_cloudflare_api_token",
"type": "secret",
"title": "Cloudflare Token",
"required": false,
"default": "",
"description": "Optional Cloudflare API token value used to populate the placeholder secret",
"needs": [
"cert_manager_create_cloudflare_secret=true"
]
},
{
"name": "cert_manager_clusterissuer_enabled",
"type": "bool",
"title": "ClusterIssuer",
"required": false,
"default": false,
"description": "Create a production Let's Encrypt ClusterIssuer that uses Cloudflare DNS-01"
},
{
"name": "cert_manager_clusterissuer_name",
"type": "str",
"title": "ClusterIssuer Name",
"required": false,
"default": "letsencrypt-production",
"needs": [
"cert_manager_clusterissuer_enabled=true"
],
"config": {
"placeholder": "letsencrypt-production"
}
},
{
"name": "cert_manager_acme_email",
"type": "str",
"title": "ACME Email",
"required": false,
"default": "",
"description": "Email address used for ACME registration",
"needs": [
"cert_manager_clusterissuer_enabled=true"
],
"config": {
"placeholder": "me@example.com"
}
},
{
"name": "cert_manager_acme_server",
"type": "str",
"title": "ACME Server",
"required": false,
"default": "https://acme-v02.api.letsencrypt.org/directory",
"description": "ACME directory URL used by the ClusterIssuer",
"needs": [
"cert_manager_clusterissuer_enabled=true"
],
"config": {
"placeholder": "https://acme-v02.api.letsencrypt.org/directory"
}
}
]
},
{
"title": "Metrics Server",
"name": "metrics-server",
"items": [
{
"name": "metrics_server_install_enabled",
"type": "bool",
"title": "Install Metrics Server",
"required": false,
"default": false
},
{
"name": "metrics_server_chart_version",
"type": "str",
"title": "Chart Version",
"required": false,
"default": "3.13.0",
"needs": [
"metrics_server_install_enabled=true"
],
"config": {
"placeholder": "3.13.0"
}
},
{
"name": "metrics_server_namespace",
"type": "str",
"title": "Namespace",
"required": false,
"default": "kube-system",
"needs": [
"metrics_server_install_enabled=true"
],
"config": {
"placeholder": "kube-system"
}
},
{
"name": "metrics_server_kubelet_insecure_tls",
"type": "bool",
"title": "Insecure TLS",
"required": false,
"default": true,
"description": "Add --kubelet-insecure-tls for clusters that use self-signed kubelet certificates",
"needs": [
"metrics_server_install_enabled=true"
]
}
]
},
{
"title": "Longhorn",
"name": "longhorn",
"items": [
{
"name": "longhorn_prerequisites_enabled",
"type": "bool",
"title": "Prepare Nodes",
"required": false,
"default": false,
"description": "Install host packages and kernel module configuration needed by Longhorn"
},
{
"name": "longhorn_node_hosts",
"type": "str",
"title": "Node Hosts",
"required": false,
"default": "k3s_cluster",
"description": "Inventory host pattern for the nodes that should be prepared for Longhorn",
"needs": [
"longhorn_prerequisites_enabled=true"
],
"config": {
"placeholder": "k3s_cluster"
}
},
{
"name": "longhorn_disable_multipathd",
"type": "bool",
"title": "Disable multipathd",
"required": false,
"default": false,
"description": "Stop and disable multipathd if your nodes do not use multipath storage",
"needs": [
"longhorn_prerequisites_enabled=true"
]
},
{
"name": "longhorn_install_enabled",
"type": "bool",
"title": "Install Longhorn",
"required": false,
"default": false,
"description": "Install Longhorn with Helm after prerequisites are in place"
},
{
"name": "longhorn_chart_version",
"type": "str",
"title": "Chart Version",
"required": false,
"default": "1.9.1",
"needs": [
"longhorn_install_enabled=true"
],
"config": {
"placeholder": "1.9.1"
}
},
{
"name": "longhorn_namespace",
"type": "str",
"title": "Namespace",
"required": false,
"default": "longhorn-system",
"needs": [
"longhorn_install_enabled=true"
],
"config": {
"placeholder": "longhorn-system"
}
},
{
"name": "longhorn_default_data_path",
"type": "str",
"title": "Data Path",
"required": false,
"default": "/var/lib/longhorn",
"description": "Default host path used by Longhorn for replica data",
"needs": [
"longhorn_install_enabled=true"
],
"config": {
"placeholder": "/var/lib/longhorn"
}
}
]
},
{
"title": "Kube-System",
"name": "kube-system",
"items": [
{
"name": "kube_proxy_strict_arp_enabled",
"type": "bool",
"title": "Enable strictARP",
"required": false,
"default": false,
"description": "Patch the kube-proxy ConfigMap and restart the daemonset with strictARP enabled"
}
]
}
]
}
@@ -1,200 +0,0 @@
---
- name: Bootstrap K3s cluster with k3sup
hosts: all
gather_facts: false
<%- if secrets_file %>
vars_files:
- << secrets_file >>
<%- endif %>
vars:
k3s_server_target: "{{ hostvars['<< k3s_server_host >>'].ansible_host | default('<< k3s_server_host >>') }}"
<%- if kubeconfig_destination_mode == 'remote' %>
k3s_generated_kubeconfig_path: "/tmp/<< k3s_context_name | default('homelab-k3s') >>-kubeconfig"
<%- else %>
k3s_generated_kubeconfig_path: "<< local_kubeconfig_path >>"
<%- endif %>
tasks:
- name: Gather control machine facts
ansible.builtin.setup:
delegate_to: localhost
run_once: true
register: localhost_facts
- name: Validate that the first server host exists in inventory
ansible.builtin.assert:
that:
- "'<< k3s_server_host >>' in hostvars"
fail_msg: "Inventory host '<< k3s_server_host >>' was not found."
run_once: true
- name: Resolve k3sup release asset
ansible.builtin.set_fact:
k3sup_release_asset: >-
{%- if localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['x86_64', 'amd64'] -%}
k3sup
{%- elif localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['aarch64', 'arm64'] -%}
k3sup-arm64
{%- elif localhost_facts.ansible_facts.ansible_system == 'Linux' and localhost_facts.ansible_facts.ansible_architecture in ['armv7l', 'armv6l'] -%}
k3sup-arm
{%- elif localhost_facts.ansible_facts.ansible_system == 'Darwin' and localhost_facts.ansible_facts.ansible_architecture in ['x86_64', 'amd64'] -%}
k3sup-darwin
{%- elif localhost_facts.ansible_facts.ansible_system == 'Darwin' and localhost_facts.ansible_facts.ansible_architecture in ['arm64', 'aarch64'] -%}
k3sup-darwin-arm64
{%- else -%}
unsupported
{%- endif -%}
when: install_k3sup_binary | default(false)
run_once: true
- name: Fail when k3sup binary installation is not supported for this control machine
ansible.builtin.fail:
msg: "Automatic k3sup installation is not supported for {{ localhost_facts.ansible_facts.ansible_system }}/{{ localhost_facts.ansible_facts.ansible_architecture }}. Set install_k3sup_binary=false and install k3sup manually."
when:
- install_k3sup_binary | default(false)
- k3sup_release_asset == "unsupported"
run_once: true
- name: Ensure local directory for k3sup exists
ansible.builtin.file:
path: "{{ '<< k3sup_install_path >>' | dirname }}"
state: directory
mode: "0755"
delegate_to: localhost
become: true
when: install_k3sup_binary | default(false)
run_once: true
- name: Install k3sup binary on the control machine
ansible.builtin.get_url:
url: "https://github.com/alexellis/k3sup/releases/download/<< k3sup_version >>/{{ k3sup_release_asset }}"
dest: "<< k3sup_install_path >>"
mode: "0755"
delegate_to: localhost
become: true
when: install_k3sup_binary | default(false)
run_once: true
- name: Verify k3sup is available
ansible.builtin.command:
cmd: "<< k3sup_install_path >> version"
changed_when: false
delegate_to: localhost
run_once: true
- name: Ensure local kubeconfig directory exists when using local destination mode
ansible.builtin.file:
path: "{{ k3s_generated_kubeconfig_path | dirname }}"
state: directory
mode: "0700"
delegate_to: localhost
when: "<< kubeconfig_destination_mode >>" == "local"
run_once: true
- name: Check whether K3s is already present on the first server
ansible.builtin.stat:
path: /etc/rancher/k3s/k3s.yaml
delegate_to: "<< k3s_server_host >>"
register: k3s_server_install_state
run_once: true
- name: Bootstrap the first K3s server with k3sup
ansible.builtin.command:
cmd: >-
<< k3sup_install_path >> install
--ip {{ k3s_server_target }}
--user << k3s_server_user | default('ubuntu') >>
--ssh-port << k3s_server_ssh_port | default(22) >>
--local-path {{ k3s_generated_kubeconfig_path }}
--merge=false
--context << k3s_context_name | default('homelab-k3s') >>
<%- if k3sup_use_sudo %>
--sudo
<%- endif %>
<%- if k3s_server_ssh_key_path %>
--ssh-key << k3s_server_ssh_key_path | quote >>
<%- endif %>
<%- if extra_server_args %>
--k3s-extra-args << extra_server_args | quote >>
<%- endif %>
delegate_to: localhost
when:
- bootstrap_control_plane | default(true)
- not (k3s_server_install_state.stat.exists | default(false))
run_once: true
- name: Resolve inventory hosts for K3s agents
ansible.builtin.set_fact:
k3s_agent_inventory_hosts: "{{ query('inventory_hostnames', '<< k3s_agent_hosts_pattern >>') }}"
when: join_agent_nodes | default(true)
run_once: true
- name: Fail when agent join is enabled but no hosts match the inventory pattern
ansible.builtin.assert:
that:
- k3s_agent_inventory_hosts | length > 0
fail_msg: "No inventory hosts matched '<< k3s_agent_hosts_pattern >>'."
when: join_agent_nodes | default(true)
run_once: true
- name: Check whether K3s agent is already present on each node
ansible.builtin.stat:
path: /etc/systemd/system/k3s-agent.service
delegate_to: "{{ item }}"
loop: "{{ k3s_agent_inventory_hosts | default([]) }}"
loop_control:
label: "{{ item }}"
register: k3s_agent_install_state
when: join_agent_nodes | default(true)
run_once: true
- name: Join agent nodes to the K3s cluster with k3sup
ansible.builtin.command:
cmd: >-
<< k3sup_install_path >> join
--ip {{ hostvars[item.item].ansible_host | default(item.item) }}
--server-ip {{ k3s_server_target }}
--user << k3s_agent_user | default('ubuntu') >>
--server-user << k3s_server_user | default('ubuntu') >>
--ssh-port << k3s_agent_ssh_port | default(22) >>
--server-ssh-port << k3s_server_ssh_port | default(22) >>
<%- if k3sup_use_sudo %>
--sudo
<%- endif %>
<%- if k3s_server_ssh_key_path %>
--ssh-key << k3s_server_ssh_key_path | quote >>
<%- endif %>
<%- if extra_agent_args %>
--k3s-extra-args << extra_agent_args | quote >>
<%- endif %>
delegate_to: localhost
loop: "{{ k3s_agent_install_state.results | default([]) }}"
loop_control:
label: "{{ item.item }}"
when:
- join_agent_nodes | default(true)
- not (item.stat.exists | default(false))
run_once: true
- name: Ensure remote kubeconfig directory exists
ansible.builtin.file:
path: "{{ '<< kubeconfig_remote_path >>' | dirname }}"
state: directory
mode: "0700"
delegate_to: "<< kubeconfig_remote_host | default('localhost') >>"
become: << kubeconfig_remote_become | default(false) >>
when: "<< kubeconfig_destination_mode >>" == "remote"
run_once: true
- name: Copy kubeconfig to the remote destination host
ansible.builtin.copy:
src: "{{ k3s_generated_kubeconfig_path }}"
dest: "<< kubeconfig_remote_path >>"
owner: "<< kubeconfig_remote_owner | default('ubuntu') >>"
group: "<< kubeconfig_remote_group | default('ubuntu') >>"
mode: "<< kubeconfig_remote_mode | default('0600') >>"
delegate_to: "<< kubeconfig_remote_host | default('localhost') >>"
become: << kubeconfig_remote_become | default(false) >>
when: "<< kubeconfig_destination_mode >>" == "remote"
run_once: true
@@ -1,353 +0,0 @@
{
"slug": "k3sup-k3s-cluster",
"kind": "ansible",
"metadata": {
"name": "Bootstrap K3s Cluster with k3sup",
"description": "Bootstraps a K3s cluster from the Ansible control machine by using k3sup over SSH. Supports first-server install, optional agent joins, and local or remote kubeconfig placement.",
"tags": [
"kubernetes",
"k3s",
"k3sup",
"bootstrap"
],
"icon": {
"provider": "simple-icons",
"id": "kubernetes"
},
"draft": true,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/k3sup-k3s-cluster"
}
},
"variables": [
{
"title": "Execution",
"name": "execution",
"items": [
{
"name": "secrets_file",
"type": "str",
"title": "Secrets File",
"required": false,
"default": "",
"description": "Optional vars file to load before running the playbook."
},
{
"name": "install_k3sup_binary",
"type": "bool",
"title": "Install k3sup",
"required": false,
"default": false,
"description": "Download and install the k3sup binary on the Ansible control machine before bootstrapping"
},
{
"name": "k3sup_version",
"type": "str",
"title": "k3sup Version",
"required": false,
"default": "0.13.15",
"description": "Version of k3sup to install when binary installation is enabled",
"needs": [
"install_k3sup_binary=true"
],
"config": {
"placeholder": "0.13.15"
}
},
{
"name": "k3sup_install_path",
"type": "str",
"title": "k3sup Path",
"required": false,
"default": "/usr/local/bin/k3sup",
"description": "Local filesystem path for the k3sup binary",
"config": {
"placeholder": "/usr/local/bin/k3sup"
}
}
]
},
{
"title": "Control Plane",
"name": "control-plane",
"items": [
{
"name": "bootstrap_control_plane",
"type": "bool",
"title": "Bootstrap Server",
"required": false,
"default": true,
"description": "Run k3sup install against the first K3s server"
},
{
"name": "k3s_server_host",
"type": "str",
"title": "Server Host",
"required": true,
"default": "k3s-server-01",
"description": "Inventory host name for the first K3s server",
"config": {
"placeholder": "k3s-server-01"
}
},
{
"name": "k3s_server_user",
"type": "str",
"title": "Server SSH User",
"required": false,
"default": "ubuntu",
"description": "SSH user used by k3sup for the first server",
"config": {
"placeholder": "ubuntu"
}
},
{
"name": "k3s_server_ssh_port",
"type": "int",
"title": "Server SSH Port",
"required": false,
"default": 22,
"description": "SSH port used for the first server",
"config": {
"slider": true,
"min": 1,
"max": 65535,
"step": 1,
"placeholder": "22"
}
},
{
"name": "k3s_server_ssh_key_path",
"type": "str",
"title": "SSH Key Path",
"required": false,
"default": "",
"description": "Optional private SSH key path for k3sup",
"config": {
"placeholder": "~/.ssh/id_ed25519"
}
},
{
"name": "k3s_context_name",
"type": "str",
"title": "Cluster Context",
"required": false,
"default": "homelab-k3s",
"description": "kubectl context name written into the generated kubeconfig",
"config": {
"placeholder": "homelab-k3s"
}
},
{
"name": "k3sup_use_sudo",
"type": "bool",
"title": "Use sudo",
"required": false,
"default": true,
"description": "Run k3sup remote operations with sudo on the target hosts"
},
{
"name": "extra_server_args",
"type": "str",
"title": "Server Args",
"required": false,
"default": "--write-kubeconfig-mode 644 --disable servicelb",
"description": "Optional extra K3s server arguments passed through k3sup",
"config": {
"placeholder": "--write-kubeconfig-mode 644 --disable servicelb"
}
}
]
},
{
"title": "Agents",
"name": "agents",
"items": [
{
"name": "join_agent_nodes",
"type": "bool",
"title": "Join Agents",
"required": false,
"default": true,
"description": "Join matching inventory hosts to the cluster as K3s agents"
},
{
"name": "k3s_agent_hosts_pattern",
"type": "str",
"title": "Agent Hosts",
"required": false,
"default": "k3s_agents",
"description": "Inventory host pattern used to discover K3s agent nodes",
"needs": [
"join_agent_nodes=true"
],
"config": {
"placeholder": "k3s_agents"
}
},
{
"name": "k3s_agent_user",
"type": "str",
"title": "Agent SSH User",
"required": false,
"default": "ubuntu",
"description": "SSH user used by k3sup for agent nodes",
"needs": [
"join_agent_nodes=true"
],
"config": {
"placeholder": "ubuntu"
}
},
{
"name": "k3s_agent_ssh_port",
"type": "int",
"title": "Agent SSH Port",
"required": false,
"default": 22,
"description": "SSH port used for agent nodes",
"needs": [
"join_agent_nodes=true"
],
"config": {
"slider": true,
"min": 1,
"max": 65535,
"step": 1,
"placeholder": "22"
}
},
{
"name": "extra_agent_args",
"type": "str",
"title": "Agent Args",
"required": false,
"default": "",
"description": "Optional extra K3s agent arguments passed through k3sup",
"needs": [
"join_agent_nodes=true"
],
"config": {
"placeholder": "--node-label role=worker"
}
}
]
},
{
"title": "Kubeconfig",
"name": "kubeconfig",
"items": [
{
"name": "kubeconfig_destination_mode",
"type": "enum",
"title": "Destination Mode",
"required": false,
"default": "local",
"description": "Store the generated kubeconfig locally on the control machine or copy it to another managed host",
"config": {
"options": [
"local",
"remote"
]
}
},
{
"name": "local_kubeconfig_path",
"type": "str",
"title": "Local Kubeconfig",
"required": false,
"default": "{{ lookup('env', 'HOME') }}/.kube/config",
"description": "Local kubeconfig path used when destination mode is local",
"needs": [
"kubeconfig_destination_mode=local"
],
"config": {
"placeholder": "{{ lookup('env', 'HOME') }}/.kube/config"
}
},
{
"name": "kubeconfig_remote_host",
"type": "str",
"title": "Remote Host",
"required": false,
"default": "localhost",
"description": "Managed inventory host that should receive the kubeconfig copy",
"needs": [
"kubeconfig_destination_mode=remote"
],
"config": {
"placeholder": "bastion-01"
}
},
{
"name": "kubeconfig_remote_path",
"type": "str",
"title": "Remote Kubeconfig",
"required": false,
"default": "/home/ubuntu/.kube/config",
"description": "Destination path on the remote host for the kubeconfig file",
"needs": [
"kubeconfig_destination_mode=remote"
],
"config": {
"placeholder": "/home/ubuntu/.kube/config"
}
},
{
"name": "kubeconfig_remote_owner",
"type": "str",
"title": "Remote Owner",
"required": false,
"default": "ubuntu",
"description": "Owner for the remote kubeconfig file",
"needs": [
"kubeconfig_destination_mode=remote"
],
"config": {
"placeholder": "ubuntu"
}
},
{
"name": "kubeconfig_remote_group",
"type": "str",
"title": "Remote Group",
"required": false,
"default": "ubuntu",
"description": "Group for the remote kubeconfig file",
"needs": [
"kubeconfig_destination_mode=remote"
],
"config": {
"placeholder": "ubuntu"
}
},
{
"name": "kubeconfig_remote_mode",
"type": "str",
"title": "Remote Mode",
"required": false,
"default": "0600",
"description": "Filesystem mode for the remote kubeconfig file",
"needs": [
"kubeconfig_destination_mode=remote"
],
"config": {
"placeholder": "0600"
}
},
{
"name": "kubeconfig_remote_become",
"type": "bool",
"title": "Remote Become",
"required": false,
"default": false,
"description": "Use become when creating directories or copying the remote kubeconfig",
"needs": [
"kubeconfig_destination_mode=remote"
]
}
]
}
]
}
@@ -1,24 +0,0 @@
---
- name: Add SSH authorized keys
hosts: all
become: true
tasks:
- name: Install SSH public key
ansible.posix.authorized_key:
user: "<< target_user >>"
state: present
key: "<< ssh_public_key >>"
manage_dir: true
<%- if configure_passwordless_sudo %>
- name: Enable passwordless sudo for the sudo group
ansible.builtin.copy:
dest: /etc/sudoers.d/90-sudo-nopasswd
content: |
%sudo ALL=(ALL:ALL) NOPASSWD: ALL
owner: root
group: root
mode: "0440"
validate: /usr/sbin/visudo -cf %s
<%- endif %>
@@ -1,55 +0,0 @@
{
"slug": "ubuntu-add-sshkey",
"kind": "ansible",
"metadata": {
"name": "Add SSH Key and Configure Sudoers",
"description": "Installs a single SSH public key for a target user on the workload-selected hosts and can enable passwordless sudo for the Ubuntu sudo group.",
"tags": [],
"icon": {
"provider": "simple-icons",
"id": "ansible"
},
"draft": false,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/ubuntu-add-sshkey"
}
},
"variables": [
{
"title": "Access",
"name": "access",
"items": [
{
"name": "target_user",
"type": "str",
"title": "Target User",
"required": false,
"default": "ubuntu",
"description": "Linux user account that should receive the authorized SSH keys.",
"config": {
"placeholder": "ubuntu"
}
},
{
"name": "ssh_public_key",
"type": "str",
"title": "SSH Public Key",
"required": true,
"description": "SSH public key to add to the target user's authorized_keys file.",
"config": {
"textarea": true,
"placeholder": "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBExampleKey user@laptop"
}
},
{
"name": "configure_passwordless_sudo",
"type": "bool",
"title": "Passwordless Sudo",
"required": false,
"default": true
}
]
}
]
}
@@ -1,85 +0,0 @@
---
- name: Install curated Ubuntu packages
hosts: all
become: true
gather_facts: true
vars:
ubuntu_package_matrix:
- name: python3-venv
enabled: << install_python3_venv >>
- name: python3-pip
enabled: << install_python3_pip >>
- name: python3-apt
enabled: << install_python3_apt >>
- name: curl
enabled: << install_curl >>
- name: wget
enabled: << install_wget >>
- name: git
enabled: << install_git >>
- name: jq
enabled: << install_jq >>
- name: rsync
enabled: << install_rsync >>
- name: unzip
enabled: << install_unzip >>
- name: ca-certificates
enabled: << install_ca_certificates >>
- name: gnupg
enabled: << install_gnupg >>
- name: lsb-release
enabled: << install_lsb_release >>
- name: prometheus-node-exporter
enabled: << install_prometheus_node_exporter >>
- name: nfs-common
enabled: << install_nfs_common >>
- name: qemu-guest-agent
enabled: << install_qemu_guest_agent >>
tasks:
- name: Validate Ubuntu APT target
ansible.builtin.assert:
that:
- ansible_facts['distribution'] == 'Ubuntu'
- ansible_facts['pkg_mgr'] == 'apt'
fail_msg: "This playbook only supports Ubuntu hosts that use APT."
- name: Build selected package list
ansible.builtin.set_fact:
ubuntu_selected_packages: "{{ ubuntu_package_matrix | selectattr('enabled') | map(attribute='name') | list }}"
- name: Refresh APT package index
ansible.builtin.command:
cmd: apt-get update
environment:
DEBIAN_FRONTEND: noninteractive
register: ubuntu_packages_apt_update
changed_when: false
- name: Install selected Ubuntu packages
ansible.builtin.apt:
name: "{{ ubuntu_selected_packages }}"
state: present
update_cache: false
when: ubuntu_selected_packages | length > 0
- name: Enable and start Prometheus node exporter
ansible.builtin.service:
name: prometheus-node-exporter
state: started
enabled: true
when: << install_prometheus_node_exporter >>
- name: Enable and start QEMU guest agent
ansible.builtin.service:
name: qemu-guest-agent
state: started
enabled: true
when: << install_qemu_guest_agent >>
- name: Show selected package summary
ansible.builtin.debug:
msg:
- "Host: {{ inventory_hostname }}"
- "Selected packages: {{ ubuntu_selected_packages | join(', ') if (ubuntu_selected_packages | length > 0) else 'none' }}"
@@ -1,157 +0,0 @@
{
"slug": "ubuntu-packages",
"kind": "ansible",
"metadata": {
"name": "Install Ubuntu Packages",
"description": "Installs a curated set of useful Ubuntu packages with per-package switches for scripting, package management, monitoring, NFS, and VM guest integration.",
"tags": [
"ubuntu",
"apt",
"packages",
"ansible"
],
"icon": {
"provider": "simple-icons",
"id": "ansible"
},
"draft": false,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/ubuntu-packages"
}
},
"variables": [
{
"title": "Base Packages",
"name": "base_packages",
"items": [
{
"name": "install_python3_venv",
"type": "bool",
"title": "python3-venv",
"required": false,
"default": true,
"description": "Install Python virtual environment support."
},
{
"name": "install_python3_pip",
"type": "bool",
"title": "python3-pip",
"required": false,
"default": true,
"description": "Install pip for Python 3."
},
{
"name": "install_python3_apt",
"type": "bool",
"title": "python3-apt",
"required": false,
"default": true,
"description": "Install Python bindings for APT."
},
{
"name": "install_curl",
"type": "bool",
"title": "curl",
"required": false,
"default": true,
"description": "Install curl for HTTP requests and downloads."
},
{
"name": "install_wget",
"type": "bool",
"title": "wget",
"required": false,
"default": true,
"description": "Install wget for scripted downloads."
},
{
"name": "install_git",
"type": "bool",
"title": "git",
"required": false,
"default": true,
"description": "Install Git for source control and repository operations."
},
{
"name": "install_jq",
"type": "bool",
"title": "jq",
"required": false,
"default": true,
"description": "Install jq for JSON parsing in shell scripts."
},
{
"name": "install_rsync",
"type": "bool",
"title": "rsync",
"required": false,
"default": true,
"description": "Install rsync for file synchronization and copy jobs."
},
{
"name": "install_unzip",
"type": "bool",
"title": "unzip",
"required": false,
"default": true,
"description": "Install unzip for archive extraction."
},
{
"name": "install_ca_certificates",
"type": "bool",
"title": "ca-certificates",
"required": false,
"default": true,
"description": "Install CA certificates for HTTPS trust."
},
{
"name": "install_gnupg",
"type": "bool",
"title": "gnupg",
"required": false,
"default": true,
"description": "Install GnuPG for repository keys and signing workflows."
},
{
"name": "install_lsb_release",
"type": "bool",
"title": "lsb-release",
"required": false,
"default": true,
"description": "Install lsb-release for distro detection in scripts."
}
]
},
{
"title": "Optional Packages",
"name": "optional_packages",
"items": [
{
"name": "install_prometheus_node_exporter",
"type": "bool",
"title": "prometheus-node-exporter",
"required": false,
"default": false,
"description": "Install Prometheus node exporter and enable its service."
},
{
"name": "install_nfs_common",
"type": "bool",
"title": "nfs-common",
"required": false,
"default": false,
"description": "Install NFS client utilities."
},
{
"name": "install_qemu_guest_agent",
"type": "bool",
"title": "qemu-guest-agent",
"required": false,
"default": false,
"description": "Install the QEMU guest agent and enable its service."
}
]
}
]
}
@@ -1,19 +0,0 @@
---
- name: Install Ubuntu VM core packages
hosts: all
become: true
tasks:
- name: Install packages
ansible.builtin.apt:
name:
- prometheus-node-exporter
- nfs-common
- qemu-guest-agent
update_cache: true
- name: Start guest qemu-guest-agent
ansible.builtin.service:
name: qemu-guest-agent
state: started
enabled: true
@@ -1,19 +0,0 @@
{
"slug": "ubuntu-vm-core",
"kind": "ansible",
"metadata": {
"name": "Install Ubuntu VM Core Packages",
"description": "Installs essential packages for Ubuntu virtual machines, including Prometheus node exporter, NFS client utilities, and QEMU guest agent.",
"tags": [],
"icon": {
"provider": "simple-icons",
"id": "ansible"
},
"draft": false,
"version": {
"name": "1.0.0",
"source_dep_name": "manual/ubuntu-vm-core"
}
},
"variables": []
}
@@ -1,112 +0,0 @@
---
services:
<< service_name >>:
image: docker.io/adguard/adguardhome:v0.107.74
restart: << restart_policy >>
<%- if network_mode == 'host' %>
network_mode: host
<%- elif network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %>
networks:
<%- if traefik_enabled %>
<< traefik_network >>:
<%- endif %>
<%- if network_mode == 'macvlan' %>
<< network_name >>:
ipv4_address: << network_macvlan_ipv4_address >>
<%- elif network_mode == 'bridge' %>
<< network_name >>:
<%- endif %>
<%- endif %>
<%- if not network_mode or network_mode == 'bridge' or traefik_enabled %>
ports:
<%- if not traefik_enabled %>
- "<< ports_http >>:80/tcp"
- "<< ports_https >>:443/tcp"
<%- endif %>
<%- if initial_setup %>
- "<< ports_initial >>:3000/tcp"
<%- endif %>
- "<< ports_https >>:443/udp"
- "<< ports_dns >>:53/tcp"
- "<< ports_dns >>:53/udp"
- "<< ports_tls >>:853/tcp"
- "<< ports_dnscrypt >>:5443/tcp"
- "<< ports_dnscrypt >>:5443/udp"
<%- endif %>
volumes:
<%- if volume_mode == 'mount' %>
- << volume_mount_path >>/work:/opt/adguardhome/work:rw
- << volume_mount_path >>/conf:/opt/adguardhome/conf:rw
<%- else %>
- << service_name >>_work:/opt/adguardhome/work
- << service_name >>_conf:/opt/adguardhome/conf
<%- endif %>
cap_add:
- NET_ADMIN
- NET_BIND_SERVICE
- NET_RAW
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>_web.loadBalancer.server.port=80
- traefik.http.routers.<< service_name >>_web_http.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_web_http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_web_http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>_web_https.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_web_https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_web_https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>_web_https.tls=true
- traefik.http.routers.<< service_name >>_web_https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if network_mode == 'bridge' or network_mode == 'macvlan' or traefik_enabled %>
networks:
<%- if network_mode == 'bridge' or network_mode == 'macvlan'%>
<< network_name >>:
<%- if network_external %>
external: true
<%- else %>
<%- if network_mode == 'macvlan' %>
driver: macvlan
driver_opts:
parent: << network_macvlan_parent_interface >>
ipam:
config:
- subnet: << network_macvlan_subnet >>
gateway: << network_macvlan_gateway >>
name: << network_name >>
<%- else %>
driver: bridge
<%- endif %>
<%- endif %>
<%- endif %>
<%- if traefik_enabled %>
<< traefik_network >>:
external: true
<%- endif %>
<%- endif %>
<%- if volume_mode == 'local' %>
volumes:
<< service_name >>_work:
driver: local
<< service_name >>_conf:
driver: local
<%- elif volume_mode == 'nfs' %>
volumes:
<< service_name >>_work:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/work"
<< service_name >>_conf:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/conf"
<%- endif %>
-373
View File
@@ -1,373 +0,0 @@
{
"slug": "adguardhome",
"kind": "compose",
"metadata": {
"name": "AdGuard Home",
"description": "Network-wide DNS-based ad and tracker blocker. AdGuard Home operates as a DNS server that blocks tracking domains, with support for DNS-over-HTTPS, DNS-over-TLS, DNS-over-QUIC, parental controls, and safe browsing filters.",
"tags": [
"traefik",
"network",
"volume"
],
"icon": {
"provider": "selfhst",
"id": "adguard-home"
},
"draft": false,
"version": {
"name": "v0.107.74",
"source_dep_name": "docker.io/adguard/adguardhome",
"source_dep_version": "v0.107.74"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "adguardhome",
"config": {
"placeholder": "adguardhome"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "initial_setup",
"type": "bool",
"title": "Initial Setup",
"required": false,
"default": true,
"description": "Enable initial setup wizard on port 3000 (only used without Traefik)"
}
]
},
{
"title": "Network",
"name": "network",
"items": [
{
"name": "network_mode",
"type": "str",
"title": "Network Mode",
"required": false
},
{
"name": "network_name",
"type": "str",
"title": "Network Name",
"required": false,
"default": "adguardhome_network",
"config": {
"placeholder": "adguardhome_network"
}
},
{
"name": "network_macvlan_ipv4_address",
"type": "str",
"title": "IPv4 Address",
"required": true,
"default": "IP_ADDRESS",
"needs": [
"network_mode=macvlan"
],
"config": {
"placeholder": "IP_ADDRESS"
}
},
{
"name": "network_macvlan_parent_interface",
"type": "str",
"title": "Parent Interface",
"required": true,
"default": "eth0",
"needs": [
"network_mode=macvlan"
],
"config": {
"placeholder": "eth0"
}
},
{
"name": "network_macvlan_subnet",
"type": "str",
"title": "Subnet",
"required": true,
"default": "IP_RANGE",
"needs": [
"network_mode=macvlan"
],
"config": {
"placeholder": "IP_RANGE"
}
},
{
"name": "network_macvlan_gateway",
"type": "str",
"title": "Gateway",
"required": true,
"default": "GATEWAY",
"needs": [
"network_mode=macvlan"
],
"config": {
"placeholder": "GATEWAY"
}
},
{
"name": "network_external",
"type": "bool",
"title": "External Network",
"required": false,
"default": false
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "str",
"title": "HTTP Port",
"required": false,
"default": 80,
"config": {
"placeholder": "80"
}
},
{
"name": "ports_https",
"type": "str",
"title": "HTTPS Port",
"required": false,
"default": 443,
"config": {
"placeholder": "443"
}
},
{
"name": "ports_dns",
"type": "int",
"title": "DNS Port",
"required": true,
"default": 53,
"config": {
"placeholder": "53"
}
},
{
"name": "ports_initial",
"type": "int",
"title": "Initial Setup Port",
"required": false,
"default": 3000,
"description": "Initial setup wizard port (only when Traefik is disabled)",
"needs": [
"traefik_enabled=false",
"initial_setup=true"
],
"config": {
"placeholder": "3000"
}
},
{
"name": "ports_tls",
"type": "int",
"title": "DoT Port",
"required": true,
"default": 853,
"config": {
"placeholder": "853"
}
},
{
"name": "ports_dnscrypt",
"type": "int",
"title": "DNSCrypt Port",
"required": true,
"default": 5443,
"config": {
"placeholder": "5443"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "adguardhome"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
},
{
"title": "Volume",
"name": "volume",
"items": [
{
"name": "volume_mode",
"type": "enum",
"title": "Volume Mode",
"required": true,
"default": "local",
"config": {
"options": [
"local",
"mount",
"nfs"
]
}
},
{
"name": "volume_mount_path",
"type": "str",
"title": "Volume Mount Path",
"required": true,
"default": "/data",
"needs": [
"volume_mode=mount"
],
"config": {
"placeholder": "/data"
}
},
{
"name": "volume_nfs_server",
"type": "str",
"title": "Volume Nfs Server",
"required": true,
"default": "NFS_SERVER",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "NFS_SERVER"
}
},
{
"name": "volume_nfs_path",
"type": "str",
"title": "Volume Nfs Path",
"required": true,
"default": "/export",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "/export"
}
},
{
"name": "volume_nfs_options",
"type": "str",
"title": "Volume Nfs Options",
"required": true,
"default": "rw,nolock,soft",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "rw,nolock,soft"
}
}
]
}
]
}
-62
View File
@@ -1,62 +0,0 @@
services:
<< service_name >>:
image: docker.io/grafana/alloy:v1.16.0
restart: << restart_policy >>
<%- if container_hostname %>
hostname: << container_hostname >>
<%- endif %>
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
<%- endif %>
command:
- run
- /etc/alloy/alloy.d/
- --server.http.listen-addr=127.0.0.1:12345
<%- if not traefik_enabled %>
ports:
- "<< ports_webui >>:12345"
<%- endif %>
volumes:
- << service_name >>_data:/alloy/data
- ./config:/etc/alloy/alloy.d/:ro
<%- if logs_enabled or metrics_enabled %>
- /:/rootfs:ro
- /sys:/sys:ro
<%- endif %>
<%- if logs_enabled and logs_system %>
- /run:/run:ro
- /var/log:/var/log:ro
<%- endif %>
<%- if (logs_enabled and logs_docker) or (metrics_enabled and metrics_docker) %>
- /var/lib/docker/:/var/lib/docker/:ro
<%- endif %>
<%- if metrics_enabled and metrics_system %>
- /run/udev/data:/run/udev/data:ro
<%- endif %>
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>-web.loadBalancer.server.port=12345
- traefik.http.routers.<< service_name >>-http.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>-https.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>-https.tls=true
- traefik.http.routers.<< service_name >>-https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
external: true
<%- endif %>
volumes:
<< service_name >>_data:
driver: local
@@ -1,4 +0,0 @@
logging {
level = "info"
format = "logfmt"
}
@@ -1,23 +0,0 @@
<%- if logs_enabled and logs_docker %>
discovery.docker "dockerlogs" {
host = "unix:///var/run/docker.sock"
}
discovery.relabel "dockerlogs" {
targets = []
rule {
source_labels = ["__meta_docker_container_name"]
regex = "/(.*)"
target_label = "service_name"
}
}
loki.source.docker "default" {
host = "unix:///var/run/docker.sock"
targets = discovery.docker.dockerlogs.targets
labels = {"platform" = "docker"}
relabel_rules = discovery.relabel.dockerlogs.rules
forward_to = [loki.write.default.receiver]
}
<%- endif %>
@@ -1,44 +0,0 @@
<%- if logs_enabled and logs_system %>
loki.source.journal "journal" {
max_age = "24h0m0s"
relabel_rules = discovery.relabel.journal.rules
forward_to = [loki.write.default.receiver]
labels = {component = string.format("%s-journal", constants.hostname)}
// NOTE: This is important to fix https://github.com/grafana/alloy/issues/924
path = "/var/log/journal"
}
local.file_match "system" {
path_targets = [{
__address__ = "localhost",
__path__ = "/var/log/{syslog,messages,*.log}",
instance = constants.hostname,
job = string.format("%s-logs", constants.hostname),
}]
}
discovery.relabel "journal" {
targets = []
rule {
source_labels = ["__journal__systemd_unit"]
target_label = "unit"
}
rule {
source_labels = ["__journal__boot_id"]
target_label = "boot_id"
}
rule {
source_labels = ["__journal__transport"]
target_label = "transport"
}
rule {
source_labels = ["__journal_priority_keyword"]
target_label = "level"
}
}
loki.source.file "system" {
targets = local.file_match.system.targets
forward_to = [loki.write.default.receiver]
}
<%- endif %>
@@ -1,23 +0,0 @@
<%- if metrics_enabled and metrics_docker %>
prometheus.exporter.cadvisor "dockermetrics" {
docker_host = "unix:///var/run/docker.sock"
storage_duration = "5m"
}
/* Relabel component to drop container_spec metrics that often have NaN values */
prometheus.relabel "docker_filter" {
forward_to = [prometheus.remote_write.default.receiver]
rule {
source_labels = ["__name__"]
regex = "container_spec_(cpu_period|cpu_quota|cpu_shares|memory_limit_bytes|memory_swap_limit_bytes|memory_reservation_limit_bytes)"
action = "drop"
}
}
prometheus.scrape "dockermetrics" {
targets = prometheus.exporter.cadvisor.dockermetrics.targets
forward_to = [prometheus.relabel.docker_filter.receiver]
scrape_interval = "10s"
}
<%- endif %>
@@ -1,36 +0,0 @@
<%- if metrics_enabled and metrics_system %>
discovery.relabel "metrics" {
targets = prometheus.exporter.unix.metrics.targets
rule {
target_label = "instance"
replacement = constants.hostname
}
rule {
target_label = "job"
replacement = string.format("%s-metrics", constants.hostname)
}
}
prometheus.exporter.unix "metrics" {
disable_collectors = ["ipvs", "btrfs", "infiniband", "xfs", "zfs"]
enable_collectors = ["meminfo"]
rootfs_path = "/rootfs"
filesystem {
fs_types_exclude = "^(autofs|binfmt_misc|bpf|cgroup2?|configfs|debugfs|devpts|devtmpfs|tmpfs|fusectl|hugetlbfs|iso9660|mqueue|nsfs|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|selinuxfs|squashfs|sysfs|tracefs)$"
mount_points_exclude = "^/(dev|proc|run/credentials/.+|sys|var/lib/docker/.+)($|/)"
mount_timeout = "5s"
}
netclass {
ignored_devices = "^(veth.*|cali.*|[a-f0-9]{15})$"
}
netdev {
device_exclude = "^(veth.*|cali.*|[a-f0-9]{15})$"
}
}
prometheus.scrape "metrics" {
scrape_interval = "15s"
targets = discovery.relabel.metrics.output
forward_to = [prometheus.remote_write.default.receiver]
}
<%- endif %>
@@ -1,20 +0,0 @@
<%- if logs_enabled %>
loki.write "default" {
endpoint {
url = "<< logs_loki_url >>"
// Batching configuration to reduce request volume and prevent rate limiting
batch_wait = "5s" // Wait up to 5 seconds before sending
batch_size = "1MiB" // Send when batch reaches 1MB (1048576 bytes)
}
external_labels = {}
}
<%- endif %>
<%- if metrics_enabled %>
prometheus.remote_write "default" {
endpoint {
url = "<< metrics_prometheus_url >>"
}
}
<%- endif %>
-239
View File
@@ -1,239 +0,0 @@
{
"slug": "alloy",
"kind": "compose",
"metadata": {
"name": "Grafana Alloy",
"description": "OpenTelemetry collector by Grafana that collects, processes, and exports metrics and logs to various backends like Prometheus and Loki.",
"tags": [
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "grafana-alloy"
},
"draft": false,
"version": {
"name": "v1.16.0",
"source_dep_name": "docker.io/grafana/alloy",
"source_dep_version": "v1.16.0"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "alloy",
"config": {
"placeholder": "alloy"
}
},
{
"name": "container_hostname",
"type": "str",
"title": "Hostname",
"required": false
},
{
"name": "restart_policy",
"type": "enum",
"title": "Container restart policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
}
]
},
{
"title": "Log Collection",
"toggle": "logs_enabled",
"name": "logs",
"items": [
{
"name": "logs_docker",
"type": "bool",
"title": "Docker Logs",
"required": false,
"default": true
},
{
"name": "logs_enabled",
"type": "bool",
"title": "Logs",
"required": false,
"default": false
},
{
"name": "logs_loki_url",
"type": "str",
"title": "Loki URL",
"required": true,
"default": "http://loki:3100/loki/api/v1/push",
"description": "Loki endpoint URL for sending logs",
"config": {
"placeholder": "http://loki:3100/loki/api/v1/push"
}
},
{
"name": "logs_system",
"type": "bool",
"title": "System Logs",
"required": false,
"default": true
}
]
},
{
"title": "Metrics Collection",
"toggle": "metrics_enabled",
"name": "metrics",
"items": [
{
"name": "metrics_docker",
"type": "bool",
"title": "Docker Metrics",
"required": false,
"default": true
},
{
"name": "metrics_enabled",
"type": "bool",
"title": "Metrics",
"required": false,
"default": false
},
{
"name": "metrics_prometheus_url",
"type": "str",
"title": "Prometheus URL",
"required": true,
"default": "http://prometheus:9090/api/v1/write",
"config": {
"placeholder": "http://prometheus:9090/api/v1/write"
}
},
{
"name": "metrics_system",
"type": "bool",
"title": "System Metrics",
"required": false,
"default": true
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_webui",
"type": "int",
"title": "Web UI Port",
"required": false,
"default": 12345,
"description": "Port for Alloy web UI",
"config": {
"placeholder": "12345"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "alloy"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Traefik network name",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": true
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
}
]
}
-2
View File
@@ -1,2 +0,0 @@
# Arcane agent token generated by Arcane when creating a remote environment.
ARCANE_AGENT_TOKEN='<< agent_token >>'
@@ -1,46 +0,0 @@
---
services:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane-agent' >>:
image: ghcr.io/getarcaneapp/agent:v2.1.0
restart: << restart_policy >>
<%- if container_name is string and container_name and (container_name | lower) not in ['true', 'false'] %>
container_name: << container_name >>
<%- endif %>
environment:
<%- if connection_mode == 'direct' %>
AGENT_MODE: "true"
<%- elif connection_mode == 'edge' %>
EDGE_AGENT: "true"
<%- endif %>
EDGE_TRANSPORT: "<< edge_transport >>"
AGENT_TOKEN: "${ARCANE_AGENT_TOKEN}"
MANAGER_API_URL: "<< manager_api_url >>"
TZ: "<< container_timezone >>"
volumes:
- << docker_socket_path >>:/var/run/docker.sock
<%- if volume_mode == 'mount' and volume_mount_path %>
- << volume_mount_path >>:/app/data
<%- else %>
- << service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane-agent' >>-data:/app/data
<%- endif %>
<%- if project_mount_path %>
- << project_mount_path >>:/app/data/projects
<%- endif %>
<%- if connection_mode == 'direct' %>
ports:
- "<< ports_agent >>:3553"
<%- endif %>
<%- if volume_mode == 'local' or (volume_mode == 'mount' and not volume_mount_path) %>
volumes:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane-agent' >>-data:
driver: local
<%- elif volume_mode == 'nfs' %>
volumes:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane-agent' >>-data:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>"
<%- endif %>
-247
View File
@@ -1,247 +0,0 @@
{
"slug": "arcane-agent",
"kind": "compose",
"metadata": {
"name": "Arcane Agent",
"description": "Remote Docker host agent for Arcane manager environments, supporting direct inbound and edge outbound connection modes.",
"tags": [
"docker",
"management",
"agent"
],
"icon": {
"provider": "selfhst",
"id": "arcane",
"color": "violet"
},
"draft": false,
"version": {
"name": "v2.1.0",
"source_dep_name": "ghcr.io/getarcaneapp/agent",
"source_dep_version": "v2.1.0"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"description": "Name of the Docker Compose service. Leave empty to omit/fall back to Arcane defaults.",
"config": {
"placeholder": "arcane-agent"
}
},
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": false,
"description": "Name to use for the container. Leave empty to omit/fall back to Arcane defaults.",
"config": {
"placeholder": "arcane-agent"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "docker_socket_path",
"type": "str",
"title": "Docker Socket Path",
"required": true,
"default": "/var/run/docker.sock",
"config": {
"placeholder": "/var/run/docker.sock"
}
}
]
},
{
"title": "Advanced",
"name": "advanced",
"items": [
{
"name": "project_mount_path",
"type": "str",
"title": "Project Mount Path",
"required": false,
"description": "Optional host path mounted to /app/data/projects for agent-visible project/Compose stack storage. Leave empty to skip this mount.",
"config": {
"placeholder": "/path/to/projects"
}
}
]
},
{
"title": "Agent Connection",
"name": "connection",
"items": [
{
"name": "connection_mode",
"type": "enum",
"title": "Connection Mode",
"required": false,
"default": "direct",
"description": "Direct exposes TCP 3553 for the manager to connect inbound. Edge connects outbound from the agent to the manager.",
"config": {
"options": [
"direct",
"edge"
]
}
},
{
"name": "edge_transport",
"type": "enum",
"title": "Transport Mode",
"required": false,
"default": "poll",
"description": "Arcane agent transport. Official generated snippets default to poll; auto keeps a live tunnel when possible.",
"config": {
"options": [
"poll",
"auto"
]
}
},
{
"name": "manager_api_url",
"type": "str",
"title": "Manager API URL",
"required": true,
"default": "http://arcane:3552",
"description": "Arcane manager API URL reachable from the agent.",
"config": {
"placeholder": "https://arcane.example.com"
}
},
{
"name": "agent_token",
"type": "secret",
"title": "Agent Token",
"required": true,
"description": "Agent token generated by Arcane when creating the remote environment."
}
]
},
{
"title": "Storage",
"name": "storage",
"items": [
{
"name": "volume_mode",
"type": "enum",
"title": "Volume Mode",
"required": false,
"default": "local",
"config": {
"options": [
"local",
"mount",
"nfs"
]
}
},
{
"name": "volume_mount_path",
"type": "str",
"title": "Data Mount Path",
"required": false,
"default": "",
"description": "Host path for agent data when using mount mode.",
"needs": [
"volume_mode=mount"
],
"config": {
"placeholder": "/opt/arcane-agent/data"
}
},
{
"name": "volume_nfs_server",
"type": "str",
"title": "NFS Server",
"required": false,
"default": "",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "nas.home.arpa"
}
},
{
"name": "volume_nfs_path",
"type": "str",
"title": "NFS Path",
"required": false,
"default": "",
"description": "NFS export path for Arcane agent data.",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "/export/app"
}
},
{
"name": "volume_nfs_options",
"type": "str",
"title": "NFS Options",
"required": false,
"default": "rw",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "rw"
}
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_agent",
"type": "int",
"title": "Agent Port",
"required": false,
"default": 3553,
"needs": [
"connection_mode=direct"
],
"config": {
"placeholder": "3553"
}
}
]
}
]
}
-7
View File
@@ -1,7 +0,0 @@
# Arcane manager secrets
# ENCRYPTION_KEY must be 32 bytes. For a hex value, use: openssl rand -hex 32
ARCANE_ENCRYPTION_KEY='<< encryption_key >>'
ARCANE_JWT_SECRET='<< jwt_secret >>'
<%- if oidc_enabled %>
ARCANE_OIDC_CLIENT_SECRET='<< oidc_client_secret >>'
<%- endif %>
-100
View File
@@ -1,100 +0,0 @@
---
services:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>:
image: ghcr.io/getarcaneapp/manager:v2.1.0
restart: << restart_policy >>
<%- if container_name is string and container_name and (container_name | lower) not in ['true', 'false'] %>
container_name: << container_name >>
<%- endif %>
environment:
APP_URL: "<< app_url >>"
<%- if puid %>
PUID: "<< puid >>"
<%- endif %>
<%- if pgid %>
PGID: "<< pgid >>"
<%- endif %>
ENCRYPTION_KEY: "${ARCANE_ENCRYPTION_KEY}"
JWT_SECRET: "${ARCANE_JWT_SECRET}"
TZ: "<< container_timezone >>"
<%- if analytics_enabled %>
ANALYTICS_DISABLED: "false"
<%- endif %>
<%- if oidc_enabled %>
OIDC_ENABLED: "true"
OIDC_CLIENT_ID: "<< oidc_client_id >>"
OIDC_CLIENT_SECRET: "${ARCANE_OIDC_CLIENT_SECRET}"
OIDC_ISSUER_URL: "<< oidc_issuer_url >>"
<%- if oidc_provider_name %>
OIDC_PROVIDER_NAME: "<< oidc_provider_name >>"
<%- endif %>
<%- if oidc_scopes %>
OIDC_SCOPES: "<< oidc_scopes >>"
<%- endif %>
<%- if oidc_groups_claim %>
OIDC_GROUPS_CLAIM: "<< oidc_groups_claim >>"
<%- endif %>
<%- if oidc_role_mappings %>
OIDC_ROLE_MAPPINGS: '<< oidc_role_mappings >>'
<%- endif %>
<%- endif %>
volumes:
- << docker_socket_path >>:/var/run/docker.sock
<%- if volume_mode == 'mount' and volume_mount_path %>
- << volume_mount_path >>:/app/data
<%- else %>
- << service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-data:/app/data
<%- endif %>
<%- if project_mount_path %>
- << project_mount_path >>:/app/data/projects
<%- endif %>
<%- if builds_mount_path %>
- << builds_mount_path >>:/builds
<%- endif %>
<%- if backups_mount_path %>
- << backups_mount_path >>:/backups
<%- endif %>
<%- if traefik_enabled %>
networks:
- << traefik_network >>
<%- endif %>
<%- if not traefik_enabled %>
ports:
- "<< ports_http >>:3552"
<%- endif %>
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-web.loadBalancer.server.port=3552
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-http.service=<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-web
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-https.service=<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-web
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-https.tls=true
- traefik.http.routers.<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if volume_mode == 'local' or (volume_mode == 'mount' and not volume_mount_path) %>
volumes:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-data:
driver: local
<%- elif volume_mode == 'nfs' %>
volumes:
<< service_name if service_name is string and service_name and (service_name | lower) not in ['true', 'false'] else 'arcane' >>-data:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>"
<%- endif %>
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
external: true
<%- endif %>
-451
View File
@@ -1,451 +0,0 @@
{
"slug": "arcane",
"kind": "compose",
"metadata": {
"name": "Arcane",
"description": "Modern self-hosted Docker management UI for containers, images, volumes, networks, Docker Compose projects, and remote environments.",
"tags": [
"docker",
"management",
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "arcane",
"color": "violet"
},
"draft": false,
"version": {
"name": "v2.1.0",
"source_dep_name": "ghcr.io/getarcaneapp/manager",
"source_dep_version": "v2.1.0"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"config": {
"placeholder": "arcane"
}
},
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": false,
"config": {
"placeholder": "arcane"
}
},
{
"name": "app_url",
"type": "str",
"title": "Application URL",
"required": true,
"default": "http://localhost:3552",
"config": {
"placeholder": "https://arcane.example.com"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "puid",
"type": "int",
"title": "PUID",
"required": false,
"config": {
"placeholder": "1000"
}
},
{
"name": "pgid",
"type": "int",
"title": "PGID",
"required": false,
"config": {
"placeholder": "1000"
}
},
{
"name": "docker_socket_path",
"type": "str",
"title": "Docker Socket Path",
"required": true,
"default": "/var/run/docker.sock",
"config": {
"placeholder": "/var/run/docker.sock"
}
},
{
"name": "analytics_enabled",
"type": "bool",
"title": "Enable Analytics",
"required": false
}
]
},
{
"title": "Advanced",
"name": "advanced",
"items": [
{
"name": "project_mount_path",
"type": "str",
"title": "Project Mount Path",
"required": false,
"description": "Optional host path mounted to /app/data/projects for Arcane project/Compose stack storage. Leave empty to skip this mount.",
"config": {
"placeholder": "/path/to/projects"
}
},
{
"name": "builds_mount_path",
"type": "str",
"title": "Builds Mount Path",
"required": false,
"description": "Optional host path mounted to /builds for Arcane build workspace files. Leave empty to skip this mount.",
"config": {
"placeholder": "/path/to/builds"
}
},
{
"name": "backups_mount_path",
"type": "str",
"title": "Backups Mount Path",
"required": false,
"description": "Optional host path mounted to /backups for Arcane volume backups. Leave empty to skip this mount.",
"config": {
"placeholder": "/path/to/backups"
}
}
]
},
{
"title": "Secrets",
"name": "secrets",
"items": [
{
"name": "encryption_key",
"type": "secret",
"title": "Encryption Key",
"required": true,
"description": "Arcane ENCRYPTION_KEY. It must be 32 bytes; a 64-character hex value from `openssl rand -hex 32` is recommended.",
"config": {
"autogenerated": {
"length": 64
}
}
},
{
"name": "jwt_secret",
"type": "secret",
"title": "JWT Secret",
"required": true,
"config": {
"autogenerated": {
"length": 64
}
}
}
]
},
{
"title": "Storage",
"name": "storage",
"items": [
{
"name": "volume_mode",
"type": "enum",
"title": "Volume Mode",
"required": false,
"default": "local",
"config": {
"options": [
"local",
"mount",
"nfs"
]
}
},
{
"name": "volume_mount_path",
"type": "str",
"title": "Data Mount Path",
"required": false,
"default": "",
"needs": [
"volume_mode=mount"
],
"config": {
"placeholder": "/opt/arcane/data"
}
},
{
"name": "volume_nfs_server",
"type": "str",
"title": "NFS Server",
"required": false,
"default": "",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "nas.home.arpa"
}
},
{
"name": "volume_nfs_path",
"type": "str",
"title": "NFS Path",
"required": false,
"default": "",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "/export/app"
}
},
{
"name": "volume_nfs_options",
"type": "str",
"title": "NFS Options",
"required": false,
"default": "rw",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "rw"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "arcane"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"needs": [
"traefik_tls_enabled=true"
],
"config": {
"placeholder": "websecure"
}
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"needs": [
"traefik_tls_enabled=true"
],
"config": {
"placeholder": "cloudflare"
}
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "int",
"title": "HTTP Port",
"required": false,
"default": 3552,
"needs": [
"traefik_enabled=false"
],
"config": {
"placeholder": "3552"
}
}
]
},
{
"title": "OIDC Single Sign-On",
"name": "oidc",
"toggle": "oidc_enabled",
"items": [
{
"name": "oidc_enabled",
"type": "bool",
"title": "OIDC",
"required": false,
"default": false
},
{
"name": "oidc_client_id",
"type": "str",
"title": "Client ID",
"required": true,
"needs": [
"oidc_enabled=true"
]
},
{
"name": "oidc_client_secret",
"type": "secret",
"title": "Client Secret",
"required": true,
"needs": [
"oidc_enabled=true"
]
},
{
"name": "oidc_issuer_url",
"type": "str",
"title": "Issuer URL",
"required": true,
"description": "Issuer URL of your OIDC provider. Arcane's docs recommend no trailing slash.",
"needs": [
"oidc_enabled=true"
],
"config": {
"placeholder": "https://auth.example.com/application/o/arcane"
}
},
{
"name": "oidc_provider_name",
"type": "str",
"title": "Provider Name",
"required": false,
"needs": [
"oidc_enabled=true"
],
"config": {
"placeholder": "My Identity Provider"
}
},
{
"name": "oidc_scopes",
"type": "str",
"title": "Scopes",
"required": false,
"description": "Optional OIDC scopes to request. Arcane defaults to openid email profile if unset; include your groups scope when using group mappings.",
"needs": [
"oidc_enabled=true"
],
"config": {
"placeholder": "openid email profile groups"
}
},
{
"name": "oidc_groups_claim",
"type": "str",
"title": "Groups Claim",
"required": false,
"needs": [
"oidc_enabled=true"
],
"config": {
"placeholder": "groups"
}
},
{
"name": "oidc_role_mappings",
"type": "str",
"title": "Role Mappings",
"required": false,
"description": "Optional JSON array for OIDC group-to-role mappings, for example [{\"claimValue\":\"docker-admins\",\"roleId\":\"role_admin\"}].",
"needs": [
"oidc_enabled=true"
]
}
]
}
]
}
-10
View File
@@ -1,10 +0,0 @@
AUTHENTIK_SECRET_KEY='<< authentik_secret_key >>'
DATABASE_USER='<< database_user >>'
DATABASE_PASSWORD='<< database_password >>'
<%- if email_enabled %>
EMAIL_USERNAME='<< email_username >>'
EMAIL_PASSWORD='<< email_password >>'
<%- endif %>
<%- if authentik_admin_password %>
AUTHENTIK_BOOTSTRAP_PASSWORD='<< authentik_admin_password >>'
<%- endif %>
-210
View File
@@ -1,210 +0,0 @@
---
services:
<< service_name >>:
image: ghcr.io/goauthentik/server:2026.2.2
restart: << restart_policy >>
command: server
environment:
- AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
- AUTHENTIK_ERROR_REPORTING__ENABLED=<< authentik_error_reporting | lower >>
<%- if database_external %>
- AUTHENTIK_POSTGRESQL__HOST=<< database_host >>
- AUTHENTIK_POSTGRESQL__PORT=<< database_port >>
<%- else %>
- AUTHENTIK_POSTGRESQL__HOST=<< service_name >>_postgres
- AUTHENTIK_POSTGRESQL__PORT=5432
<%- endif %>
- AUTHENTIK_POSTGRESQL__USER=${DATABASE_USER}
- AUTHENTIK_POSTGRESQL__NAME=<< database_name >>
- AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
<%- if email_enabled %>
- AUTHENTIK_EMAIL__HOST=<< email_host >>
- AUTHENTIK_EMAIL__PORT=<< email_port >>
- AUTHENTIK_EMAIL__FROM=<< email_from >>
- AUTHENTIK_EMAIL__USERNAME=${EMAIL_USERNAME}
- AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
<%- if email_encryption == "ssl" %>
- AUTHENTIK_EMAIL__USE_SSL=True
<%- elif email_encryption == "starttls" %>
- AUTHENTIK_EMAIL__USE_TLS=True
<%- endif %>
<%- endif %>
networks:
<%- if traefik_enabled %>
- << traefik_network >>
<%- endif %>
- << service_name >>_backend
<%- if not traefik_enabled %>
ports:
- "<< ports_http >>:9000"
- "<< ports_https >>:9443"
<%- endif %>
volumes:
<%- if volume_mode == 'mount' %>
- << volume_mount_path >>/data:/data
- << volume_mount_path >>/templates:/templates
<%- elif volume_mode == 'local' or volume_mode == 'nfs' %>
- << service_name >>_data:/data
- << service_name >>_templates:/templates
<%- endif %>
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>_web.loadBalancer.server.port=9000
- traefik.http.routers.<< service_name >>_http.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_http.entrypoints=<< traefik_entrypoint >>
- "traefik.http.middlewares.authentik.forwardauth.address=\
http://<< service_name >>:9000/outpost.goauthentik.io/auth/traefik"
- "traefik.http.middlewares.authentik.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.authentik.forwardauth.authResponseHeaders=\
X-authentik-username,\
X-authentik-groups,\
X-authentik-entitlements,\
X-authentik-email,\
X-authentik-name,\
X-authentik-uid,\
X-authentik-jwt,\
X-authentik-meta-jwks,\
X-authentik-meta-outpost,\
X-authentik-meta-provider,\
X-authentik-meta-app,\
X-authentik-meta-version"
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>_https.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>_https.tls=true
- traefik.http.routers.<< service_name >>_https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if not database_external %>
depends_on:
- << service_name >>_postgres
<%- endif %>
<< service_name >>_worker:
image: ghcr.io/goauthentik/server:2026.2.2
restart: << restart_policy >>
command: worker
environment:
- AUTHENTIK_SECRET_KEY=${AUTHENTIK_SECRET_KEY}
- AUTHENTIK_ERROR_REPORTING__ENABLED=<< authentik_error_reporting | lower >>
<%- if database_external %>
- AUTHENTIK_POSTGRESQL__HOST=<< database_host >>
- AUTHENTIK_POSTGRESQL__PORT=<< database_port >>
<%- else %>
- AUTHENTIK_POSTGRESQL__HOST=<< service_name >>_postgres
- AUTHENTIK_POSTGRESQL__PORT=5432
<%- endif %>
- AUTHENTIK_POSTGRESQL__USER=${DATABASE_USER}
- AUTHENTIK_POSTGRESQL__NAME=<< database_name >>
- AUTHENTIK_POSTGRESQL__PASSWORD=${DATABASE_PASSWORD}
<%- if email_enabled %>
- AUTHENTIK_EMAIL__HOST=<< email_host >>
- AUTHENTIK_EMAIL__PORT=<< email_port >>
- AUTHENTIK_EMAIL__FROM=<< email_from >>
- AUTHENTIK_EMAIL__USERNAME=${EMAIL_USERNAME}
- AUTHENTIK_EMAIL__PASSWORD=${EMAIL_PASSWORD}
<%- if email_encryption == "ssl" %>
- AUTHENTIK_EMAIL__USE_SSL=True
<%- elif email_encryption == "starttls" %>
- AUTHENTIK_EMAIL__USE_TLS=True
<%- endif %>
<%- endif %>
<%- if authentik_admin_password %>
- AUTHENTIK_BOOTSTRAP_PASSWORD=${AUTHENTIK_BOOTSTRAP_PASSWORD}
<%- endif %>
user: root
networks:
- << service_name >>_backend
volumes:
- /run/docker.sock:/run/docker.sock
<%- if volume_mode == 'mount' %>
- << volume_mount_path >>/data:/data
- << volume_mount_path >>/certs:/certs
- << volume_mount_path >>/templates:/templates
<%- elif volume_mode == 'local' or volume_mode == 'nfs' %>
- << service_name >>_data:/data
- << service_name >>_certs:/certs
- << service_name >>_templates:/templates
<%- endif %>
<%- if not database_external %>
depends_on:
- << service_name >>_postgres
<%- endif %>
<%- if not database_external %>
<< service_name >>_postgres:
image: docker.io/library/postgres:17.9
restart: << restart_policy >>
environment:
- POSTGRES_USER=${DATABASE_USER}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
- POSTGRES_DB=<< database_name >>
healthcheck:
test: ["CMD-SHELL", "pg_isready -U << database_user >>"]
start_period: 30s
interval: 10s
timeout: 10s
retries: 5
networks:
- << service_name >>_backend
volumes:
<%- if volume_mode == 'mount' %>
- << volume_mount_path >>/postgres:/var/lib/postgresql/data
<%- elif volume_mode == 'local' or volume_mode == 'nfs' %>
- << service_name >>_postgres:/var/lib/postgresql/data
<%- endif %>
<%- endif %>
networks:
<< service_name >>_backend:
driver: bridge
<%- if traefik_enabled %>
<< traefik_network >>:
external: true
<%- endif %>
<%- if volume_mode == 'local' %>
volumes:
<%- if not database_external %>
<< service_name >>_postgres:
driver: local
<%- endif %>
<< service_name >>_data:
driver: local
<< service_name >>_certs:
driver: local
<< service_name >>_templates:
driver: local
<%- elif volume_mode == 'nfs' %>
volumes:
<%- if not database_external %>
<< service_name >>_postgres:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/postgres"
<%- endif %>
<< service_name >>_data:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/data"
<< service_name >>_certs:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/certs"
<< service_name >>_templates:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,nfsvers=4,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/templates"
<%- endif %>
-402
View File
@@ -1,402 +0,0 @@
{
"slug": "authentik-compose",
"kind": "compose",
"metadata": {
"name": "Authentik",
"description": "Open-source Identity Provider (IdP) that supports OAuth2, SAML, LDAP, and SCIM for single sign-on and user authentication across your applications.",
"tags": [
"traefik",
"volume"
],
"icon": {
"provider": "selfhst",
"id": "authentik",
"color": "default"
},
"draft": false,
"version": {
"name": "2026.2.2"
}
},
"variables": [
{
"name": "general",
"title": "General",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "authentik",
"config": {
"placeholder": "authentik"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
}
]
},
{
"name": "database",
"title": "Database",
"items": [
{
"name": "database_password",
"type": "secret",
"title": "Database password",
"required": true,
"config": {
"autogenerated": {}
}
},
{
"name": "database_user",
"type": "str",
"title": "The database user",
"required": false,
"default": "authentik",
"config": {
"placeholder": "authentik"
}
},
{
"name": "database_name",
"type": "str",
"title": "The database name",
"required": false,
"default": "authentik",
"config": {
"placeholder": "authentik"
}
},
{
"name": "database_external",
"type": "bool",
"title": "External Database",
"required": false,
"default": false
},
{
"name": "database_host",
"type": "str",
"title": "External database host",
"required": false,
"default": "",
"needs": [
"database_external=true"
],
"config": {
"placeholder": "postgres.home.arpa"
}
},
{
"name": "database_port",
"type": "int",
"title": "External database port",
"required": false,
"default": 5432,
"config": {
"placeholder": "5432"
},
"needs": [
"database_external=true"
]
}
]
},
{
"name": "ports",
"title": "Ports",
"items": [
{
"name": "ports_http",
"type": "str",
"title": "HTTP Port",
"required": false,
"default": 8000,
"config": {
"placeholder": "8000"
}
},
{
"name": "ports_https",
"type": "str",
"title": "HTTPS Port",
"required": false,
"default": 8443,
"config": {
"placeholder": "8443"
}
}
]
},
{
"name": "traefik",
"title": "Traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "authentik"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
},
{
"name": "authentik",
"title": "Authentik",
"items": [
{
"name": "authentik_secret_key",
"type": "secret",
"title": "Secret Key",
"required": true,
"config": {
"autogenerated": {}
}
},
{
"name": "authentik_admin_password",
"type": "secret",
"title": "Admin Password",
"required": false,
"config": {
"autogenerated": {}
}
},
{
"name": "authentik_error_reporting",
"type": "bool",
"title": "Error Reporting",
"required": false
}
]
},
{
"name": "volume",
"title": "Volume",
"items": [
{
"name": "volume_mode",
"type": "enum",
"title": "Volume Mode",
"required": true,
"default": "local",
"config": {
"options": [
"local",
"mount",
"nfs"
]
}
},
{
"name": "volume_mount_path",
"type": "str",
"title": "Volume Mount Path",
"required": true,
"default": "/data",
"config": {
"placeholder": "/data"
},
"needs": [
"volume_mode=mount"
]
},
{
"name": "volume_nfs_server",
"type": "str",
"title": "Volume Nfs Server",
"required": true,
"default": "NFS_SERVER",
"config": {
"placeholder": "NFS_SERVER"
},
"needs": [
"volume_mode=nfs"
]
},
{
"name": "volume_nfs_path",
"type": "str",
"title": "Volume Nfs Path",
"required": true,
"default": "/export",
"config": {
"placeholder": "/export"
},
"needs": [
"volume_mode=nfs"
]
},
{
"name": "volume_nfs_options",
"type": "str",
"title": "Volume Nfs Options",
"required": true,
"default": "rw,nolock,soft",
"config": {
"placeholder": "rw,nolock,soft"
},
"needs": [
"volume_mode=nfs"
]
}
]
},
{
"name": "email",
"title": "Email",
"toggle": "email_enabled",
"items": [
{
"name": "email_host",
"type": "str",
"title": "SMTP server hostname",
"required": true,
"config": {
"placeholder": "smtp.home.arpa"
}
},
{
"name": "email_port",
"type": "int",
"title": "SMTP server port",
"required": true,
"default": 25,
"config": {
"placeholder": "25"
}
},
{
"name": "email_username",
"type": "str",
"title": "SMTP username",
"required": true,
"config": {
"placeholder": "user@example.com"
}
},
{
"name": "email_password",
"type": "secret",
"title": "SMTP password",
"required": true
},
{
"name": "email_from",
"type": "str",
"title": "From email address",
"required": true,
"config": {
"placeholder": "noreply@example.com"
}
},
{
"name": "email_enabled",
"type": "bool",
"title": "Email",
"required": false,
"default": false
},
{
"name": "email_encryption",
"type": "str",
"title": "Encryption",
"required": false,
"default": "tls",
"config": {
"placeholder": "tls"
}
}
]
}
]
}
-87
View File
@@ -1,87 +0,0 @@
services:
<< service_name >>:
image: docker.io/ubuntu/bind9:9.20-24.10_edge
restart: << restart_policy >>
<%- if container_hostname %>
hostname: << container_hostname >>
<%- endif %>
environment:
- TZ=<< container_timezone >>
- BIND9_USER=bind
<%- if network_mode == 'host' %>
network_mode: host
<%- elif network_mode == 'bridge' or network_mode == 'macvlan' %>
networks:
<%- if network_mode == 'macvlan' %>
<< network_name >>:
ipv4_address: << network_macvlan_ipv4_address >>
<%- elif network_mode == 'bridge' %>
<< network_name >>:
<%- endif %>
<%- endif %>
<%- if network_mode == '' or network_mode == 'bridge' %>
ports:
- "53:53/tcp"
- "53:53/udp"
<%- endif %>
volumes:
<%- if volume_mode == 'mount' %>
- << volume_mount_path >>/config:/etc/bind:rw
- << volume_mount_path >>/zones:/var/lib/bind:rw
- << volume_mount_path >>/cache:/var/cache/bind:rw
<%- else %>
- << service_name >>_config:/etc/bind
- << service_name >>_zones:/var/lib/bind
- << service_name >>_cache:/var/cache/bind
<%- endif %>
<%- if network_mode == 'bridge' or network_mode == 'macvlan' %>
networks:
<< network_name >>:
<%- if network_external %>
external: true
<%- else %>
<%- if network_mode == 'macvlan' %>
driver: macvlan
driver_opts:
parent: << network_macvlan_parent_interface >>
ipam:
config:
- subnet: << network_macvlan_subnet >>
gateway: << network_macvlan_gateway >>
name: << network_name >>
<%- elif network_mode == 'bridge' %>
driver: bridge
<%- endif %>
<%- endif %>
<%- endif %>
<%- if volume_mode == 'local' %>
volumes:
<< service_name >>_config:
driver: local
<< service_name >>_zones:
driver: local
<< service_name >>_cache:
driver: local
<%- elif volume_mode == 'nfs' %>
volumes:
<< service_name >>_config:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/config"
<< service_name >>_zones:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/zones"
<< service_name >>_cache:
driver: local
driver_opts:
type: nfs
o: addr=<< volume_nfs_server >>,<< volume_nfs_options >>
device: ":<< volume_nfs_path >>/cache"
<%- endif %>
@@ -1,67 +0,0 @@
// BIND9 Main Configuration File
// Documentation: https://bind9.readthedocs.io/
<%- if tsig_enabled %>
include "/etc/bind/tsig.key";
<%- endif %>
acl "trusted" {
127.0.0.1;
::1;
10.0.0.0/8;
172.16.0.0/12;
192.168.0.0/16;
};
options {
directory "/var/cache/bind";
// DNS forwarders for recursive queries
forwarders {
1.1.1.1;
8.8.8.8;
};
// Allow recursion from trusted networks only
allow-recursion { trusted; };
// Allow queries from any (adjust as needed)
allow-query { any; };
// Disable zone transfers by default (enable per-zone with TSIG)
allow-transfer { none; };
// DNSSEC validation
dnssec-validation auto;
// Listen on all interfaces
listen-on { any; };
listen-on-v6 { any; };
// Disable query logging (enable for debugging)
// querylog yes;
};
// Local zones
zone "localhost" {
type master;
file "/etc/bind/db.local";
};
zone "127.in-addr.arpa" {
type master;
file "/etc/bind/db.127";
};
zone "0.in-addr.arpa" {
type master;
file "/etc/bind/db.0";
};
zone "255.in-addr.arpa" {
type master;
file "/etc/bind/db.255";
};
// Include your custom zones
include "/etc/bind/named.conf.zones";
@@ -1,14 +0,0 @@
zone "<< domain_name >>" {
type master;
file "/var/lib/bind/db.primary.zone";
<%- if tsig_enabled %>
allow-transfer { key "tsig-transfer-key"; };
allow-update { key "tsig-transfer-key"; };
<%- endif %>
<%- if dnssec_enabled %>
dnssec-policy default;
inline-signing yes;
<%- endif %>
};
@@ -1,6 +0,0 @@
<%- if tsig_enabled %>
key "tsig-transfer-key" {
algorithm hmac-sha256;
secret "<< tsig_key_secret >>";
};
<%- endif %>
@@ -1,11 +0,0 @@
$TTL 86400
@ IN SOA ns1.<< domain_name >>. admin.<< domain_name >>. (
2026010101 ; serial
3600 ; refresh
900 ; retry
604800 ; expire
86400 ; minimum
)
@ IN NS ns1.<< domain_name >>.
ns1 IN A 127.0.0.1
-262
View File
@@ -1,262 +0,0 @@
{
"slug": "bind9",
"kind": "compose",
"metadata": {
"name": "BIND9",
"description": "The most widely used authoritative and recursive DNS server. This template includes example zones, TSIG authentication for secure zone transfers, and DNSSEC support.",
"tags": [
"network",
"volume"
],
"icon": {
"provider": "selfhst",
"id": "bind-9"
},
"draft": false,
"version": {
"name": "9.20-24.10_edge",
"source_dep_name": "docker.io/ubuntu/bind9",
"source_dep_version": "9.20-24.10_edge"
}
},
"variables": [
{
"title": "dns_security",
"name": "dns-security",
"items": [
{
"name": "dnssec_enabled",
"type": "bool",
"title": "DNSSEC",
"required": false
}
]
},
{
"title": "dns_zone",
"name": "dns-zone",
"items": [
{
"name": "domain_name",
"type": "str",
"title": "Primary domain name",
"required": false,
"default": "home.arpa",
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "tsig_enabled",
"type": "bool",
"title": "TSIG",
"required": false
},
{
"name": "tsig_key_secret",
"type": "secret",
"title": "TSIG key secret",
"required": false,
"needs": [
"tsig_enabled=true"
],
"config": {
"autogenerated": {
"kind": "base64",
"bytes": 32
}
}
}
]
},
{
"title": "Volume",
"name": "volume",
"items": [
{
"name": "volume_mode",
"type": "enum",
"title": "Volume Mode",
"required": true,
"default": "local",
"config": {
"options": [
"local",
"mount",
"nfs"
]
}
},
{
"name": "volume_mount_path",
"type": "str",
"title": "Volume Mount Path",
"required": true,
"default": "/data",
"needs": [
"volume_mode=mount"
],
"config": {
"placeholder": "/data"
}
},
{
"name": "volume_nfs_server",
"type": "str",
"title": "Volume Nfs Server",
"required": true,
"default": "NFS_SERVER",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "NFS_SERVER"
}
},
{
"name": "volume_nfs_path",
"type": "str",
"title": "Volume Nfs Path",
"required": true,
"default": "/export",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "/export"
}
},
{
"name": "volume_nfs_options",
"type": "str",
"title": "Volume Nfs Options",
"required": true,
"default": "rw,nolock,soft",
"needs": [
"volume_mode=nfs"
],
"config": {
"placeholder": "rw,nolock,soft"
}
}
]
},
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "bind9",
"config": {
"placeholder": "bind9"
}
},
{
"name": "container_hostname",
"type": "str",
"title": "Container Hostname",
"required": false
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "network_mode",
"type": "enum",
"title": "Network mode",
"required": false,
"default": "bridge",
"config": {
"options": [
"bridge",
"macvlan",
"host"
]
}
},
{
"name": "network_external",
"type": "bool",
"title": "External Network",
"required": false,
"default": false
},
{
"name": "network_macvlan_parent_interface",
"type": "str",
"title": "Parent Interface",
"required": false,
"default": "",
"config": {
"placeholder": "eth0"
}
},
{
"name": "network_macvlan_gateway",
"type": "str",
"title": "Gateway",
"required": false,
"default": "",
"config": {
"placeholder": "10.20.0.1"
}
},
{
"name": "network_name",
"type": "str",
"title": "Network Name",
"required": false,
"default": "bind9_network",
"config": {
"placeholder": "bind9_network"
}
},
{
"name": "network_macvlan_subnet",
"type": "str",
"title": "Subnet",
"required": false,
"default": "",
"config": {
"placeholder": "10.20.0.0/16"
}
},
{
"name": "network_macvlan_ipv4_address",
"type": "str",
"title": "IPv4 Address",
"required": false,
"default": "",
"config": {
"placeholder": "10.20.1.50"
}
}
]
}
]
}
-7
View File
@@ -1,7 +0,0 @@
<%- if tls_enabled and tls_email %>
{
email << tls_email >>
}
<%- endif %>
import /etc/caddy/config/*.caddy
-47
View File
@@ -1,47 +0,0 @@
services:
<< service_name >>:
image: docker.io/library/caddy:2.11.2-alpine
restart: << restart_policy >>
<%- if container_timezone %>
environment:
- TZ=<< container_timezone >>
<%- endif %>
ports:
- "<< ports_http >>:80"
- "<< ports_https >>:443"
- "<< ports_https >>:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./config:/etc/caddy/config:ro
<%- if webserver_enabled %>
<%- if webserver_content_type == 'mount' %>
- << webserver_content_mount_path >>:/srv:ro
<%- elif webserver_content_type == 'nfs' %>
- caddy_site:/srv:ro
<%- endif %>
<%- endif %>
- caddy_data:/data
- caddy_config:/config
networks:
- << docker_network >>
networks:
<< docker_network >>:
<%- if docker_network_external %>
external: true
<%- else %>
driver: bridge
name: << docker_network >>
<%- endif %>
volumes:
<%- if webserver_enabled and webserver_content_type == 'nfs' %>
caddy_site:
driver: local
driver_opts:
type: nfs
o: addr=<< webserver_content_nfs_server >>,nfsvers=4,<< webserver_content_nfs_options >>
device: ":<< webserver_content_nfs_path >>"
<%- endif %>
caddy_data:
caddy_config:
@@ -1,25 +0,0 @@
<%- if webserver_enabled %>
<< webserver_domain >> {
root * /srv
file_server
<%- if webserver_custom_config_content %>
<%- for line in webserver_custom_config_content.split('\n') %>
<< line >>
<%- endfor %>
<%- endif %>
}
<%- endif %>
<%- if reverse_proxy_enabled %>
<< reverse_proxy_domain >> {
<%- if reverse_proxy_custom_config_content %>
reverse_proxy << reverse_proxy_upstream >> {
<%- for line in reverse_proxy_custom_config_content.split('\n') %>
<< line >>
<%- endfor %>
}
<%- else %>
reverse_proxy << reverse_proxy_upstream >>
<%- endif %>
}
<%- endif %>
-319
View File
@@ -1,319 +0,0 @@
{
"slug": "caddy",
"kind": "compose",
"metadata": {
"name": "Caddy",
"description": "Modern web server and reverse proxy with automatic HTTPS, modular Caddyfile config, and simple presets for static sites or upstream proxying.",
"tags": [
"reverse-proxy",
"web-server",
"tls"
],
"icon": {
"provider": "selfhst",
"id": "caddy",
"color": "emerald"
},
"draft": false,
"version": {
"name": "2.11.2-alpine",
"source_dep_name": "docker.io/library/caddy",
"source_dep_version": "2.11.2-alpine"
}
},
"variables": [
{
"name": "general",
"title": "General",
"description": "Basic container naming and lifecycle settings.",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "caddy",
"description": "Compose service name for the Caddy container.",
"config": {
"placeholder": "caddy"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"description": "Restart policy applied to the container.",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"description": "Optional timezone passed through as the `TZ` environment variable.",
"config": {
"placeholder": "UTC"
}
}
]
},
{
"name": "network",
"title": "Network",
"description": "Port publishing and Docker network attachment.",
"items": [
{
"name": "ports_http",
"type": "int",
"title": "HTTP Port",
"required": true,
"default": 80,
"description": "Host port published for HTTP traffic on container port 80.",
"config": {
"placeholder": "80"
}
},
{
"name": "ports_https",
"type": "int",
"title": "HTTPS Port",
"required": true,
"default": 443,
"description": "Host port published for HTTPS traffic on container port 443.",
"config": {
"placeholder": "443"
}
},
{
"name": "docker_network",
"type": "str",
"title": "Docker Network",
"required": true,
"default": "caddy",
"description": "Docker network that Caddy should join.",
"config": {
"placeholder": "caddy"
}
},
{
"name": "docker_network_external",
"type": "bool",
"title": "External Network",
"required": false,
"default": false
}
]
},
{
"name": "tls",
"title": "TLS",
"description": "Optional global ACME metadata for Caddy.",
"toggle": "tls_enabled",
"items": [
{
"name": "tls_enabled",
"type": "bool",
"title": "TLS Settings",
"required": false,
"default": false
},
{
"name": "tls_email",
"type": "str",
"title": "ACME Email",
"required": false,
"description": "Email address used for ACME account registration.",
"config": {
"placeholder": "admin@example.com"
},
"needs": [
"tls_enabled=true"
]
}
]
},
{
"name": "webserver",
"title": "Webserver",
"description": "Optional generated static site config that serves files from `/srv`.",
"toggle": "webserver_enabled",
"items": [
{
"name": "webserver_enabled",
"type": "bool",
"title": "Static Webserver",
"required": false,
"default": false,
"description": "Render a basic static site config into `config/webserver.caddy`."
},
{
"name": "webserver_domain",
"type": "str",
"title": "Site Address",
"required": true,
"description": "Hostname or site address used by the generated static site config.",
"config": {
"placeholder": "example.com"
},
"needs": [
"webserver_enabled=true"
]
},
{
"name": "webserver_content_type",
"type": "enum",
"title": "Site Content",
"required": true,
"default": "mount",
"description": "Storage mode used for static site content mounted into `/srv`.",
"config": {
"options": [
"mount",
"nfs"
]
},
"needs": [
"webserver_enabled=true"
]
},
{
"name": "webserver_content_mount_path",
"type": "str",
"title": "Bind Mount Path",
"required": true,
"default": "./data",
"description": "Host path used when site content is mounted directly from the filesystem.",
"config": {
"placeholder": "./data"
},
"needs": [
"webserver_enabled=true",
"webserver_content_type=mount"
]
},
{
"name": "webserver_content_nfs_server",
"type": "str",
"title": "NFS Server",
"required": true,
"default": "192.0.2.1",
"description": "Hostname or IP address of the NFS server that stores site content.",
"config": {
"placeholder": "192.0.2.1"
},
"needs": [
"webserver_enabled=true",
"webserver_content_type=nfs"
]
},
{
"name": "webserver_content_nfs_path",
"type": "str",
"title": "NFS Export Path",
"required": true,
"default": "/path/to/export",
"config": {
"placeholder": "/path/to/export"
},
"needs": [
"webserver_enabled=true",
"webserver_content_type=nfs"
]
},
{
"name": "webserver_content_nfs_options",
"type": "str",
"title": "NFS Mount Options",
"required": true,
"default": "rw,nolock,soft",
"config": {
"placeholder": "rw,nolock,soft"
},
"needs": [
"webserver_enabled=true",
"webserver_content_type=nfs"
]
},
{
"name": "webserver_custom_config_content",
"type": "str",
"title": "Custom Config Content",
"required": false,
"description": "Optional additional Caddyfile directives inserted into the generated webserver site block when not empty.",
"config": {
"placeholder": "encode zstd gzip",
"textarea": true
},
"needs": [
"webserver_enabled=true"
]
}
]
},
{
"name": "reverse-proxy",
"title": "Reverse Proxy",
"description": "Optional generated reverse proxy config for forwarding requests to an upstream service.",
"toggle": "reverse_proxy_enabled",
"items": [
{
"name": "reverse_proxy_enabled",
"type": "bool",
"title": "Reverse Proxy",
"required": false,
"default": false,
"description": "Append a reverse proxy site block to `config/webserver.caddy`."
},
{
"name": "reverse_proxy_domain",
"type": "str",
"title": "Site Address",
"required": true,
"description": "Hostname or site address used by the generated reverse proxy config.",
"config": {
"placeholder": "app.example.com"
},
"needs": [
"reverse_proxy_enabled=true"
]
},
{
"name": "reverse_proxy_upstream",
"type": "str",
"title": "Upstream",
"required": true,
"description": "Upstream target used by the generated reverse proxy config, for example `app:3000`.",
"config": {
"placeholder": "app:3000"
},
"needs": [
"reverse_proxy_enabled=true"
]
},
{
"name": "reverse_proxy_custom_config_content",
"type": "str",
"title": "Custom Config Content",
"required": false,
"description": "Optional additional `reverse_proxy` subdirectives inserted into the generated reverse proxy block when not empty.",
"config": {
"placeholder": "header_up X-Forwarded-Proto https",
"textarea": true
},
"needs": [
"reverse_proxy_enabled=true"
]
}
]
}
]
}
-2
View File
@@ -1,2 +0,0 @@
# Checkmk credentials
CMK_PASSWORD=<< cmk_password >>
-55
View File
@@ -1,55 +0,0 @@
---
services:
<< service_name >>:
image: checkmk/check-mk-community:2.5.0-latest
restart: << restart_policy >>
environment:
<%- if container_timezone %>
- TZ=<< container_timezone >>
<%- endif %>
- CMK_PASSWORD=${CMK_PASSWORD}
- CMK_SITE_ID=<< cmk_site_id >>
tmpfs:
- /opt/omd/sites/<< cmk_site_id >>/tmp:uid=<< user_uid >>,gid=<< user_gid >>
volumes:
<%- if not container_timezone %>
- /etc/localtime:/etc/localtime:ro
<%- endif %>
- << service_name >>_data:/omd/sites:rw
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
<%- endif %>
ports:
<%- if not traefik_enabled %>
- "<< ports_http >>:5000"
<%- endif %>
- "<< ports_agent >>:8000"
- "<< ports_snmp >>:162/udp"
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>-web.loadBalancer.server.port=5000
- traefik.http.services.<< service_name >>-web.loadBalancer.server.scheme=http
- traefik.http.routers.<< service_name >>-http.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>-https.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>-https.tls=true
- traefik.http.routers.<< service_name >>-https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
volumes:
<< service_name >>_data:
driver: local
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
external: true
<%- endif %>
-225
View File
@@ -1,225 +0,0 @@
{
"slug": "checkmk",
"kind": "compose",
"metadata": {
"name": "Checkmk",
"description": "Comprehensive IT monitoring solution for infrastructure, applications, and services. Provides real-time health and performance insights with server, network, cloud, and application monitoring.",
"tags": [
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "checkmk"
},
"draft": false,
"version": {
"name": "2.5.0-latest",
"source_dep_name": "checkmk/check-mk-community",
"source_dep_version": "2.5.0-latest"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "checkmk",
"config": {
"placeholder": "checkmk"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "user_uid",
"type": "int",
"title": "User Uid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "user_gid",
"type": "int",
"title": "User Gid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "cmk_password",
"type": "secret",
"title": "CheckMK admin password",
"required": true,
"config": {
"autogenerated": {
"length": 32
}
}
},
{
"name": "cmk_site_id",
"type": "str",
"title": "CheckMK site ID",
"required": true,
"default": "cmk",
"config": {
"placeholder": "cmk"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "checkmk"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "int",
"title": "HTTP Port",
"required": false,
"default": 5000,
"config": {
"placeholder": "5000"
}
},
{
"name": "ports_agent",
"type": "int",
"title": "Agent port",
"required": true,
"default": 8000,
"config": {
"placeholder": "8000"
}
},
{
"name": "ports_snmp",
"type": "int",
"title": "SNMP trap port",
"required": true,
"default": 162,
"config": {
"placeholder": "162"
}
}
]
}
]
}
-2
View File
@@ -1,2 +0,0 @@
# Cloudflared credentials
TUNNEL_TOKEN=<< tunnel_token >>
@@ -1,18 +0,0 @@
services:
<< service_name >>:
image: cloudflare/cloudflared:2026.3.0
container_name: << container_name >>
environment:
- TUNNEL_TOKEN=${TUNNEL_TOKEN}
command: << tunnel_run_command >>
networks:
- << network_name >>
restart: << restart_policy >>
networks:
<< network_name >>:
<%- if network_external %>
external: true
<%- else %>
driver: bridge
<%- endif %>
-103
View File
@@ -1,103 +0,0 @@
{
"slug": "cloudflared",
"kind": "compose",
"metadata": {
"name": "Cloudflared",
"description": "Runs a Cloudflare Tunnel connector in Docker Compose so private services can be published through Cloudflare Zero Trust.",
"tags": [],
"icon": {
"provider": "selfhst",
"id": "cloudflare-zero-trust"
},
"draft": false,
"version": {
"name": "2026.3.0",
"source_dep_name": "cloudflare/cloudflared",
"source_dep_version": "2026.3.0"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Compose service name",
"required": false,
"default": "cloudflared",
"config": {
"placeholder": "cloudflared"
}
},
{
"name": "container_name",
"type": "str",
"title": "Container name",
"required": false,
"default": "cloudflared",
"config": {
"placeholder": "cloudflared"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Container restart policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "tunnel_run_command",
"type": "str",
"title": "Command",
"required": false,
"default": "tunnel --no-autoupdate run",
"config": {
"placeholder": "tunnel --no-autoupdate run"
}
},
{
"name": "tunnel_token",
"type": "secret",
"title": "Tunnel Token",
"required": true,
"description": "Cloudflare tunnel token used by the connector."
}
]
},
{
"title": "Network",
"name": "network",
"items": [
{
"name": "network_name",
"type": "str",
"title": "Network Name",
"required": true,
"default": "proxy",
"description": "Docker network name for the tunnel container",
"config": {
"placeholder": "proxy"
}
},
{
"name": "network_external",
"type": "bool",
"title": "External Network",
"required": false,
"default": true
}
]
}
]
}
-7
View File
@@ -1,7 +0,0 @@
# CrowdSec enrollment and bouncer placeholders
<%- if console_enrollment_enabled %>
ENROLL_KEY=<< console_enrollment_key >>
<%- endif %>
<%- if bouncer_traefik_key %>
BOUNCER_KEY_TRAEFIK=<< bouncer_traefik_key >>
<%- endif %>
@@ -1,63 +0,0 @@
services:
<< service_name >>:
image: crowdsecurity/crowdsec:<< image_tag >>
container_name: << container_name >>
environment:
- TZ=<< container_timezone >>
- COLLECTIONS=<< collections_core >><%- if collections_extra %> << collections_extra >><%- endif %><%- if appsec_enabled %> crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules<%- endif %>
<%- if docker_logs_enabled %>
- GID=<< docker_group_id >>
<%- endif %>
<%- if console_enrollment_enabled %>
- ENROLL_KEY=${ENROLL_KEY}
- ENROLL_INSTANCE_NAME=<< console_enrollment_name >>
<%- if console_enrollment_tags %>
- ENROLL_TAGS=<< console_enrollment_tags >>
<%- endif %>
<%- endif %>
<%- if bouncer_traefik_key %>
- BOUNCER_KEY_TRAEFIK=${BOUNCER_KEY_TRAEFIK}
<%- endif %>
volumes:
- ./config/acquis.yaml:/etc/crowdsec/acquis.yaml:ro
- ./config/acquis.d:/etc/crowdsec/acquis.d:ro
- crowdsec_data:/var/lib/crowdsec/data
- /var/log:/var/log-host:ro
<%- if docker_logs_enabled %>
- /var/run/docker.sock:/var/run/docker.sock:ro
<%- endif %>
<%- if traefik_accesslog_enabled %>
- << traefik_accesslog_mount_path >>:/var/log/traefik:ro
<%- endif %>
<%- if lapi_expose_enabled or (appsec_enabled and appsec_expose_enabled) %>
ports:
<%- if lapi_expose_enabled %>
- "<< lapi_bind_address >>:<< lapi_port >>:8080"
<%- endif %>
<%- if appsec_enabled and appsec_expose_enabled %>
- "<< appsec_bind_address >>:<< appsec_port >>:7422"
<%- endif %>
<%- endif %>
restart: << restart_policy >>
networks:
- crowdsec
<%- if traefik_network_enabled %>
- << traefik_network >>
<%- endif %>
networks:
crowdsec:
driver: bridge
<%- if traefik_network_enabled %>
<< traefik_network >>:
<%- if traefik_network_external %>
external: true
<%- else %>
driver: bridge
name: << traefik_network >>
<%- endif %>
<%- endif %>
volumes:
crowdsec_data:
driver: local
@@ -1,9 +0,0 @@
<%- if appsec_enabled %>
source: appsec
listen_addr: 0.0.0.0:7422
path: << appsec_path >>
appsec_config: crowdsecurity/appsec-default
labels:
type: appsec
name: << appsec_name >>
<%- endif %>
@@ -1,26 +0,0 @@
<%- if host_logs_enabled %>
filenames:
- /var/log-host/auth.log
- /var/log-host/syslog
labels:
type: syslog
<%- endif %>
<%- if host_logs_enabled and (docker_logs_enabled or traefik_accesslog_enabled) %>
---
<%- endif %>
<%- if docker_logs_enabled %>
source: docker
container_name_regexp:
- << docker_container_name_regexp >>
labels:
type: << docker_log_type >>
<%- endif %>
<%- if docker_logs_enabled and traefik_accesslog_enabled %>
---
<%- endif %>
<%- if traefik_accesslog_enabled %>
filenames:
- /var/log/traefik/<< traefik_accesslog_filename >>
labels:
type: traefik
<%- endif %>
-451
View File
@@ -1,451 +0,0 @@
{
"slug": "crowdsec",
"kind": "compose",
"metadata": {
"name": "CrowdSec",
"description": "Standalone CrowdSec Security Engine deployment for a single Docker host, with host and reverse-proxy log acquisition, optional AppSec exposure, and console or bouncer enrollment placeholders.",
"tags": [
"security",
"logging",
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "crowdsec"
},
"draft": true,
"version": {
"name": "v1.7.6",
"source_dep_name": "manual/crowdsec"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "crowdsec",
"description": "Compose service name for CrowdSec.",
"config": {
"placeholder": "crowdsec"
}
},
{
"name": "container_name",
"type": "str",
"title": "Container Name",
"required": false,
"default": "crowdsec",
"description": "Container name for CrowdSec.",
"config": {
"placeholder": "crowdsec"
}
},
{
"name": "image_tag",
"type": "str",
"title": "Image Tag",
"required": true,
"default": "v1.7.6",
"description": "CrowdSec image tag to deploy.",
"config": {
"placeholder": "v1.7.6"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": true,
"default": "UTC",
"config": {
"placeholder": "UTC"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
}
]
},
{
"title": "Collections",
"name": "collections",
"items": [
{
"name": "collections_core",
"type": "str",
"title": "Core Collections",
"required": true,
"default": "crowdsecurity/linux crowdsecurity/sshd",
"description": "Space-separated base collections to install on startup.",
"config": {
"placeholder": "crowdsecurity/linux crowdsecurity/sshd"
}
},
{
"name": "collections_extra",
"type": "str",
"title": "Extra Collections",
"required": false,
"default": "crowdsecurity/traefik",
"description": "Additional space-separated collections to install, for example a reverse-proxy parser.",
"config": {
"placeholder": "crowdsecurity/traefik"
}
}
]
},
{
"title": "Acquisition",
"name": "acquisition",
"items": [
{
"name": "host_logs_enabled",
"type": "bool",
"title": "Host Logs",
"required": false,
"default": true,
"description": "Ingest host auth and syslog files from `/var/log`."
},
{
"name": "docker_logs_enabled",
"type": "bool",
"title": "Docker Logs",
"required": false,
"default": false,
"description": "Read logs from a matching Docker container through the Docker socket."
},
{
"name": "docker_group_id",
"type": "int",
"title": "Docker Group ID",
"required": true,
"default": 998,
"description": "Host Docker group ID so the container can access the socket.",
"needs": [
"docker_logs_enabled=true"
],
"config": {
"placeholder": "998"
}
},
{
"name": "docker_container_name_regexp",
"type": "str",
"title": "Docker Container Regex",
"required": true,
"default": ".*traefik.*",
"description": "Regular expression used to match the Docker container whose logs should be parsed.",
"needs": [
"docker_logs_enabled=true"
],
"config": {
"placeholder": ".*traefik.*"
}
},
{
"name": "docker_log_type",
"type": "str",
"title": "Docker Log Type",
"required": true,
"default": "traefik",
"description": "Parser type label for the Docker datasource.",
"needs": [
"docker_logs_enabled=true"
],
"config": {
"placeholder": "traefik"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"items": [
{
"name": "traefik_network_enabled",
"type": "bool",
"title": "Shared Traefik Network",
"required": false,
"default": false,
"description": "Attach CrowdSec to an existing Docker network shared with Traefik or other bouncers."
},
{
"name": "traefik_network",
"type": "str",
"title": "Traefik Network",
"required": true,
"default": "traefik",
"description": "Docker network shared with Traefik or other remediation components.",
"needs": [
"traefik_network_enabled=true"
],
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_network_external",
"type": "bool",
"title": "External Network",
"required": false,
"default": true,
"needs": [
"traefik_network_enabled=true"
]
},
{
"name": "traefik_accesslog_enabled",
"type": "bool",
"title": "Traefik Access Log",
"required": false,
"default": false,
"description": "Read Traefik access logs from a mounted host path."
},
{
"name": "traefik_accesslog_mount_path",
"type": "str",
"title": "Traefik Log Path",
"required": true,
"default": "/var/log/traefik",
"description": "Host directory containing Traefik access logs.",
"needs": [
"traefik_accesslog_enabled=true"
],
"config": {
"placeholder": "/var/log/traefik"
}
},
{
"name": "traefik_accesslog_filename",
"type": "str",
"title": "Traefik Log File",
"required": true,
"default": "access.log",
"description": "Filename of the Traefik access log inside the mounted directory.",
"needs": [
"traefik_accesslog_enabled=true"
],
"config": {
"placeholder": "access.log"
}
}
]
},
{
"title": "Local API",
"toggle": "lapi_expose_enabled",
"name": "lapi",
"items": [
{
"name": "lapi_expose_enabled",
"type": "bool",
"title": "Expose Local API",
"required": false,
"default": false,
"description": "Publish CrowdSec Local API on the host."
},
{
"name": "lapi_bind_address",
"type": "str",
"title": "Local API Bind Address",
"required": true,
"default": "127.0.0.1",
"description": "Host address used for the Local API port binding.",
"needs": [
"lapi_expose_enabled=true"
],
"config": {
"placeholder": "127.0.0.1"
}
},
{
"name": "lapi_port",
"type": "int",
"title": "Local API Port",
"required": true,
"default": 8080,
"description": "Host port used for the Local API binding.",
"needs": [
"lapi_expose_enabled=true"
],
"config": {
"placeholder": "8080"
}
}
]
},
{
"title": "AppSec",
"toggle": "appsec_enabled",
"name": "appsec",
"items": [
{
"name": "appsec_enabled",
"type": "bool",
"title": "Enable AppSec",
"required": false,
"default": false
},
{
"name": "appsec_name",
"type": "str",
"title": "AppSec Name",
"required": true,
"default": "traefik-appsec",
"description": "Friendly name for the AppSec component.",
"needs": [
"appsec_enabled=true"
],
"config": {
"placeholder": "traefik-appsec"
}
},
{
"name": "appsec_path",
"type": "str",
"title": "AppSec Path",
"required": true,
"default": "/",
"description": "Request path handled by the AppSec listener.",
"needs": [
"appsec_enabled=true"
],
"config": {
"placeholder": "/"
}
},
{
"name": "appsec_expose_enabled",
"type": "bool",
"title": "Expose AppSec",
"required": false,
"default": false,
"description": "Publish the AppSec listener on the host.",
"needs": [
"appsec_enabled=true"
]
},
{
"name": "appsec_bind_address",
"type": "str",
"title": "AppSec Bind Address",
"required": true,
"default": "127.0.0.1",
"description": "Host address used for the AppSec port binding.",
"needs": [
"appsec_enabled=true",
"appsec_expose_enabled=true"
],
"config": {
"placeholder": "127.0.0.1"
}
},
{
"name": "appsec_port",
"type": "int",
"title": "AppSec Port",
"required": true,
"default": 7422,
"description": "Host port used for the AppSec port binding.",
"needs": [
"appsec_enabled=true",
"appsec_expose_enabled=true"
],
"config": {
"placeholder": "7422"
}
}
]
},
{
"title": "Console Enrollment",
"toggle": "console_enrollment_enabled",
"name": "console",
"items": [
{
"name": "console_enrollment_enabled",
"type": "bool",
"title": "Console Enrollment",
"required": false,
"default": false,
"description": "Enroll this CrowdSec instance into CrowdSec Console."
},
{
"name": "console_enrollment_name",
"type": "str",
"title": "Instance Name",
"required": true,
"default": "crowdsec-homelab",
"description": "Human-readable name shown in CrowdSec Console.",
"needs": [
"console_enrollment_enabled=true"
],
"config": {
"placeholder": "crowdsec-homelab"
}
},
{
"name": "console_enrollment_tags",
"type": "str",
"title": "Enrollment Tags",
"required": false,
"default": "homelab,edge",
"description": "Comma-separated tags assigned during console enrollment.",
"needs": [
"console_enrollment_enabled=true"
],
"config": {
"placeholder": "homelab,edge"
}
},
{
"name": "console_enrollment_key",
"type": "secret",
"title": "Enrollment Key",
"required": true,
"description": "Enrollment key generated by CrowdSec Console.",
"needs": [
"console_enrollment_enabled=true"
]
}
]
},
{
"title": "Bouncers",
"name": "bouncers",
"items": [
{
"name": "bouncer_traefik_key",
"type": "secret",
"title": "Traefik Bouncer Key",
"required": false,
"description": "Optional seed key for a Traefik bouncer or plugin.",
"config": {
"autogenerated": {
"length": 32
}
}
}
]
}
]
}
-47
View File
@@ -1,47 +0,0 @@
---
services:
<< service_name >>:
image: docker.io/louislam/dockge:1.5.0
restart: << restart_policy >>
environment:
- TZ=<< container_timezone >>
- DOCKGE_STACKS_DIR=<< stacks_path >>
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- << service_name >>_data:/app/data
- << stacks_path >>:<< stacks_path >>
<%- if traefik_enabled %>
networks:
- << traefik_network >>
<%- endif %>
<%- if not traefik_enabled %>
ports:
- "<< ports_http >>:5001"
<%- endif %>
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>-web.loadBalancer.server.port=5001
- traefik.http.routers.<< service_name >>-http.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>-https.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>-https.tls=true
- traefik.http.routers.<< service_name >>-https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
volumes:
<< service_name >>_data:
driver: local
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
external: true
<%- endif %>
-175
View File
@@ -1,175 +0,0 @@
{
"slug": "dockge",
"kind": "compose",
"metadata": {
"name": "Dockge",
"description": "Web-based Docker Compose stack manager with a clean UI for creating, editing, starting, and stopping compose stacks directly from the browser.",
"tags": [
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "dockge"
},
"draft": false,
"version": {
"name": "1.5.0",
"source_dep_name": "docker.io/louislam/dockge",
"source_dep_version": "1.5.0"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "dockge",
"config": {
"placeholder": "dockge"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "stacks_path",
"type": "str",
"title": "Docker Compose Path",
"required": true,
"default": "/stacks",
"description": "Path used for Docker Compose.",
"config": {
"placeholder": "/stacks"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "dockge"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "str",
"title": "HTTP Port",
"required": false,
"default": 5001,
"config": {
"placeholder": "5001"
}
}
]
}
]
}
-7
View File
@@ -1,7 +0,0 @@
# Dockhand credentials
<%- if database_type == 'postgres' %>
DATABASE_PASSWORD=<< database_password >>
<%- endif %>
<%- if encryption_key %>
ENCRYPTION_KEY=<< encryption_key >>
<%- endif %>
@@ -1,59 +0,0 @@
services:
<< service_name >>:
image: docker.io/fnsys/dockhand:v1.0.27
restart: << restart_policy >>
<%- if database_type == 'postgres' or disable_local_login or encryption_key or puid or pgid %>
environment:
<%- if database_type == 'postgres' %>
- DATABASE_URL=postgres://<< database_user >>:${DATABASE_PASSWORD}@<< database_host >>:<< database_port >>/<< database_name >>
<%- endif %>
<%- if encryption_key %>
- ENCRYPTION_KEY=${ENCRYPTION_KEY}
<%- endif %>
<%- if puid %>
- PUID=<< puid >>
<%- endif %>
<%- if pgid %>
- PGID=<< pgid >>
<%- endif %>
<%- if disable_local_login %>
- DISABLE_LOCAL_LOGIN=<< disable_local_login | lower >>
<%- endif %>
<%- endif %>
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- dockhand_data:/app/data
<%- if traefik_enabled %>
networks:
- << traefik_network >>
<%- endif %>
<%- if not traefik_enabled %>
ports:
- "<< ports_http >>:3000"
<%- endif %>
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>-web.loadBalancer.server.port=3000
- traefik.http.routers.<< service_name >>-http.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>-https.service=<< service_name >>-web
- traefik.http.routers.<< service_name >>-https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>-https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>-https.tls=true
- traefik.http.routers.<< service_name >>-https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
volumes:
dockhand_data:
driver: local
<%- if traefik_enabled %>
networks:
<< traefik_network >>:
external: true
<%- endif %>
-290
View File
@@ -1,290 +0,0 @@
{
"slug": "dockhand",
"kind": "compose",
"metadata": {
"name": "Dockhand",
"description": "Modern Docker management UI with Compose stack support, Git-based stack deployments, and optional OIDC authentication.",
"tags": [
"traefik",
"postgresql",
"sqlite"
],
"icon": {
"provider": "selfhst",
"id": "dockhand"
},
"draft": false,
"version": {
"name": "v1.0.27",
"source_dep_name": "docker.io/fnsys/dockhand",
"source_dep_version": "v1.0.27"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "dockhand",
"config": {
"placeholder": "dockhand"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "encryption_key",
"type": "secret",
"title": "Encryption Key",
"required": false,
"description": "Base64-encoded 32-byte AES-256 key used for credential encryption.",
"config": {
"autogenerated": {
"kind": "base64",
"bytes": 32
}
}
},
{
"name": "puid",
"type": "int",
"title": "PUID",
"required": false,
"config": {
"placeholder": "1000"
},
"description": "Optional user ID for Dockhand. On Linux check with `id -u`."
},
{
"name": "pgid",
"type": "int",
"title": "PGID",
"required": false,
"config": {
"placeholder": "1000"
},
"description": "Optional group ID for Dockhand. On Linux check with `id -g`."
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "int",
"title": "HTTP Port",
"required": true,
"default": 3000,
"needs": [
"traefik_enabled=false"
],
"config": {
"placeholder": "3000"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "dockhand"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"needs": [
"traefik_tls_enabled=true"
],
"config": {
"placeholder": "cloudflare"
}
}
]
},
{
"title": "Database",
"name": "database",
"items": [
{
"name": "database_type",
"type": "enum",
"title": "Database Type",
"required": false,
"default": "sqlite",
"config": {
"options": [
"sqlite",
"postgres"
]
}
},
{
"name": "database_host",
"type": "str",
"title": "Database Host",
"required": true,
"needs": [
"database_type=postgres"
],
"default": "postgres",
"config": {
"placeholder": "postgres"
}
},
{
"name": "database_port",
"type": "int",
"title": "Database Port",
"required": true,
"needs": [
"database_type=postgres"
],
"default": 5432,
"config": {
"placeholder": "5432"
}
},
{
"name": "database_name",
"type": "str",
"title": "Database Name",
"required": true,
"needs": [
"database_type=postgres"
],
"default": "dockhand",
"config": {
"placeholder": "dockhand"
},
"description": "Database name used for PostgreSQL."
},
{
"name": "database_user",
"type": "str",
"title": "Database User",
"required": true,
"needs": [
"database_type=postgres"
],
"default": "dockhand",
"config": {
"placeholder": "dockhand"
},
"description": "Username used for PostgreSQL."
},
{
"name": "database_password",
"type": "secret",
"title": "Database Password",
"required": true,
"needs": [
"database_type=postgres"
],
"description": "Password used for PostgreSQL."
}
]
},
{
"title": "Authentication",
"name": "authentication",
"items": [
{
"name": "disable_local_login",
"type": "bool",
"title": "Disable Local Login",
"required": false,
"default": false,
"description": "when SSO correctly setup, not recommended as first deployment"
}
]
}
]
}
-5
View File
@@ -1,5 +0,0 @@
# Forgejo database credentials
<%- if database_type != 'sqlite' %>
DATABASE_USER=<< database_user >>
DATABASE_PASSWORD=<< database_password >>
<%- endif %>
-118
View File
@@ -1,118 +0,0 @@
services:
<< service_name >>:
image: codeberg.org/forgejo/forgejo:15.0.2
restart: << restart_policy >>
environment:
- USER_UID=<< user_uid >>
- USER_GID=<< user_gid >>
<%- if database_type != 'sqlite' %>
- FORGEJO__database__DB_TYPE=<< database_type >>
<%- if database_external %>
- FORGEJO__database__HOST=<< database_host >>
<%- else %>
- FORGEJO__database__HOST=<< service_name >>_db
<%- endif %>
- FORGEJO__database__NAME=<< database_name >>
- FORGEJO__database__USER=${DATABASE_USER}
- FORGEJO__database__PASSWD=${DATABASE_PASSWORD}
<%- endif %>
- FORGEJO__server__SSH_PORT=<< ports_ssh >>
<%- if not traefik_enabled %>
- FORGEJO__server__ROOT_URL=<< forgejo_url >>
<%- else %>
- FORGEJO__server__ROOT_URL=https://<< traefik_host >>.<< traefik_domain >>
<%- endif %>
<%- if not database_external and (database_type == "postgres" or database_type == "mysql") or traefik_enabled %>
networks:
<%- if not database_external and database_type != 'sqlite' %>
- << service_name >>_backend
<%- endif %>
<%- if traefik_enabled %>
- << traefik_network >>
<%- endif %>
<%- endif %>
ports:
<%- if not traefik_enabled %>
- "<< ports_http >>:3000"
<%- endif %>
- "<< ports_ssh >>:22"
volumes:
- << service_name >>_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>_web.loadBalancer.server.port=3000
- traefik.http.routers.<< service_name >>_http.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>_https.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>_https.tls=true
- traefik.http.routers.<< service_name >>_https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if not database_external and database_type != 'sqlite' %>
depends_on:
- << service_name >>_db
<%- endif %>
<%- if not database_external and database_type == "postgres" %>
<< service_name >>_db:
image: docker.io/library/postgres:18
restart: << restart_policy >>
environment:
- TZ=<< container_timezone >>
- POSTGRES_USER=${DATABASE_USER}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
- POSTGRES_DB=<< database_name >>
networks:
- << service_name >>_backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U << database_user >>"]
start_period: 30s
interval: 10s
timeout: 10s
retries: 5
volumes:
- << service_name >>_db:/var/lib/postgresql/data
<%- elif not database_external and database_type == "mysql" %>
<< service_name >>_db:
image: docker.io/library/mysql:8.1
restart: << restart_policy >>
environment:
- MYSQL_USER=${DATABASE_USER}
- MYSQL_PASSWORD=${DATABASE_PASSWORD}
- MYSQL_DATABASE=<< database_name >>
- MYSQL_ROOT_PASSWORD=${DATABASE_PASSWORD}
networks:
- << service_name >>_backend
volumes:
- << service_name >>_db:/var/lib/mysql
<%- endif %>
volumes:
<< service_name >>_data:
driver: local
<%- if not database_external %>
<%- if database_type == "postgres" or database_type == "mysql" %>
<< service_name >>_db:
driver: local
<%- endif %>
<%- endif %>
<%- if not database_external and (database_type == "postgres" or database_type == "mysql") or traefik_enabled %>
networks:
<%- if not database_external %>
<< service_name >>_backend:
driver: bridge
<%- endif %>
<%- if traefik_enabled %>
<< traefik_network >>:
external: true
<%- endif %>
<%- endif %>
-290
View File
@@ -1,290 +0,0 @@
{
"slug": "forgejo",
"kind": "compose",
"metadata": {
"name": "Forgejo",
"description": "Self-hosted lightweight software forge for Git repository hosting, code review, issue tracking, package publishing, and team collaboration.",
"tags": [
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "forgejo"
},
"draft": false,
"version": {
"name": "15.0.2",
"source_dep_name": "codeberg.org/forgejo/forgejo",
"source_dep_version": "15.0.2"
}
},
"variables": [
{
"title": "General",
"name": "general",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "forgejo",
"config": {
"placeholder": "forgejo"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "user_uid",
"type": "int",
"title": "User Uid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "user_gid",
"type": "int",
"title": "User Gid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "forgejo_url",
"type": "str",
"title": "Public URL",
"required": false,
"default": "https://forgejo.home.arpa",
"needs": [
"traefik_enabled=false"
],
"config": {
"placeholder": "https://forgejo.home.arpa"
}
}
]
},
{
"title": "Database",
"name": "database",
"items": [
{
"name": "database_type",
"type": "enum",
"title": "Database backend type",
"required": false,
"default": "sqlite",
"config": {
"options": [
"sqlite",
"postgres",
"mysql"
]
}
},
{
"name": "database_name",
"type": "str",
"title": "Database name",
"required": false,
"default": "forgejo",
"needs": [
"database_type=postgres,mysql"
],
"config": {
"placeholder": "forgejo"
}
},
{
"name": "database_user",
"type": "str",
"title": "Database user",
"required": false,
"default": "forgejo",
"needs": [
"database_type=postgres,mysql"
],
"config": {
"placeholder": "forgejo"
}
},
{
"name": "database_host",
"type": "str",
"title": "Database host",
"required": false,
"default": "postgres",
"needs": [
"database_type=postgres,mysql;database_external=true"
],
"config": {
"placeholder": "postgres"
}
},
{
"name": "database_password",
"type": "secret",
"title": "Database password",
"required": false,
"needs": [
"database_type=postgres,mysql"
],
"config": {
"autogenerated": {
"length": 32
}
}
},
{
"name": "database_external",
"type": "bool",
"title": "External Database",
"required": false,
"default": false,
"needs": [
"database_type=postgres,mysql"
]
}
]
},
{
"title": "Ports",
"name": "ports",
"items": [
{
"name": "ports_http",
"type": "str",
"title": "HTTP Port",
"required": false,
"default": 3000,
"config": {
"placeholder": "3000"
}
},
{
"name": "ports_ssh",
"type": "str",
"title": "Ports Ssh",
"required": false,
"default": 2222,
"config": {
"placeholder": "2222"
}
}
]
},
{
"title": "Traefik",
"name": "traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "forgejo"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
}
]
}
-5
View File
@@ -1,5 +0,0 @@
# Gitea database credentials
<%- if database_type != 'sqlite' %>
DATABASE_USER=<< database_user >>
DATABASE_PASSWORD=<< database_password >>
<%- endif %>
-118
View File
@@ -1,118 +0,0 @@
services:
<< service_name >>:
image: docker.io/gitea/gitea:1.26.1
restart: << restart_policy >>
environment:
- USER_UID=<< user_uid >>
- USER_GID=<< user_gid >>
<%- if database_type != 'sqlite' %>
- GITEA__database__DB_TYPE=<< database_type >>
<%- if database_external %>
- GITEA__database__HOST=<< database_host >>
<%- else %>
- GITEA__database__HOST=<< service_name >>_db
<%- endif %>
- GITEA__database__NAME=<< database_name >>
- GITEA__database__USER=${DATABASE_USER}
- GITEA__database__PASSWD=${DATABASE_PASSWORD}
<%- endif %>
- GITEA__server__SSH_PORT=<< ports_ssh >>
<%- if not traefik_enabled %>
- GITEA__server__ROOT_URL=<< gitea_url >>
<%- else %>
- GITEA__server__ROOT_URL=https://<< traefik_host >>.<< traefik_domain >>
<%- endif %>
<%- if not database_external and (database_type == "postgres" or database_type == "mysql") or traefik_enabled %>
networks:
<%- if not database_external and database_type != 'sqlite' %>
- << service_name >>_backend
<%- endif %>
<%- if traefik_enabled %>
- << traefik_network >>
<%- endif %>
<%- endif %>
ports:
<%- if not traefik_enabled %>
- "<< ports_http >>:3000"
<%- endif %>
- "<< ports_ssh >>:22"
volumes:
- << service_name >>_data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
<%- if traefik_enabled %>
labels:
- traefik.enable=true
- traefik.docker.network=<< traefik_network >>
- traefik.http.services.<< service_name >>_web.loadBalancer.server.port=3000
- traefik.http.routers.<< service_name >>_http.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_http.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_http.entrypoints=<< traefik_entrypoint >>
<%- if traefik_tls_enabled %>
- traefik.http.routers.<< service_name >>_https.service=<< service_name >>_web
- traefik.http.routers.<< service_name >>_https.rule=Host(`<< traefik_host >>.<< traefik_domain >>`)
- traefik.http.routers.<< service_name >>_https.entrypoints=<< traefik_tls_entrypoint >>
- traefik.http.routers.<< service_name >>_https.tls=true
- traefik.http.routers.<< service_name >>_https.tls.certresolver=<< traefik_tls_certresolver >>
<%- endif %>
<%- endif %>
<%- if not database_external and database_type != 'sqlite' %>
depends_on:
- << service_name >>_db
<%- endif %>
<%- if not database_external and database_type == "postgres" %>
<< service_name >>_db:
image: docker.io/library/postgres:17.9
restart: << restart_policy >>
environment:
- TZ=<< container_timezone >>
- POSTGRES_USER=${DATABASE_USER}
- POSTGRES_PASSWORD=${DATABASE_PASSWORD}
- POSTGRES_DB=<< database_name >>
networks:
- << service_name >>_backend
healthcheck:
test: ["CMD-SHELL", "pg_isready -U << database_user >>"]
start_period: 30s
interval: 10s
timeout: 10s
retries: 5
volumes:
- << service_name >>_db:/var/lib/postgresql/data
<%- elif not database_external and database_type == "mysql" %>
<< service_name >>_db:
image: docker.io/library/mysql:8.1
restart: << restart_policy >>
environment:
- MYSQL_USER=${DATABASE_USER}
- MYSQL_PASSWORD=${DATABASE_PASSWORD}
- MYSQL_DATABASE=<< database_name >>
- MYSQL_ROOT_PASSWORD=${DATABASE_PASSWORD}
networks:
- << service_name >>_backend
volumes:
- << service_name >>_db:/var/lib/mysql
<%- endif %>
volumes:
<< service_name >>_data:
driver: local
<%- if not database_external %>
<%- if database_type == "postgres" or database_type == "mysql" %>
<< service_name >>_db:
driver: local
<%- endif %>
<%- endif %>
<%- if not database_external and (database_type == "postgres" or database_type == "mysql") or traefik_enabled %>
networks:
<%- if not database_external %>
<< service_name >>_backend:
driver: bridge
<%- endif %>
<%- if traefik_enabled %>
<< traefik_network >>:
external: true
<%- endif %>
<%- endif %>
-291
View File
@@ -1,291 +0,0 @@
{
"slug": "gitea",
"kind": "compose",
"metadata": {
"name": "Gitea",
"description": "Lightweight, self-hosted Git service with a web interface for repository hosting, code review, issue tracking, and team collaboration. Written in Go.",
"tags": [
"traefik"
],
"icon": {
"provider": "selfhst",
"id": "gitea",
"color": "emerald"
},
"draft": false,
"version": {
"name": "1.26.1-1",
"source_dep_name": "docker.io/gitea/gitea",
"source_dep_version": "1.26.1"
}
},
"variables": [
{
"name": "general",
"title": "General",
"items": [
{
"name": "service_name",
"type": "str",
"title": "Service Name",
"required": false,
"default": "gitea",
"config": {
"placeholder": "gitea"
}
},
{
"name": "container_timezone",
"type": "str",
"title": "Container Timezone",
"required": false,
"config": {
"placeholder": "Europe/Berlin"
}
},
{
"name": "user_uid",
"type": "int",
"title": "User Uid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "user_gid",
"type": "int",
"title": "User Gid",
"required": false,
"default": 1000,
"config": {
"placeholder": "1000"
}
},
{
"name": "restart_policy",
"type": "enum",
"title": "Restart Policy",
"required": true,
"default": "unless-stopped",
"config": {
"options": [
"unless-stopped",
"always",
"on-failure",
"no"
]
}
},
{
"name": "gitea_url",
"type": "str",
"title": "Public URL",
"required": false,
"default": "https://git.home.arpa",
"config": {
"placeholder": "https://git.home.arpa"
},
"needs": [
"traefik_enabled=false"
]
}
]
},
{
"name": "database",
"title": "Database",
"items": [
{
"name": "database_type",
"type": "enum",
"title": "Database backend type",
"required": false,
"default": "sqlite",
"config": {
"options": [
"sqlite",
"postgres",
"mysql"
]
}
},
{
"name": "database_name",
"type": "str",
"title": "Database name",
"required": false,
"default": "gitea",
"config": {
"placeholder": "gitea"
},
"needs": [
"database_type=postgres,mysql"
]
},
{
"name": "database_user",
"type": "str",
"title": "Database user",
"required": false,
"default": "gitea",
"config": {
"placeholder": "gitea"
},
"needs": [
"database_type=postgres,mysql"
]
},
{
"name": "database_host",
"type": "str",
"title": "Database host",
"required": false,
"default": "postgres",
"config": {
"placeholder": "postgres"
},
"needs": [
"database_type=postgres,mysql;database_external=true"
]
},
{
"name": "database_password",
"type": "secret",
"title": "Database password",
"required": false,
"config": {
"autogenerated": {
"length": 32
}
},
"needs": [
"database_type=postgres,mysql"
]
},
{
"name": "database_external",
"type": "bool",
"title": "External Database",
"required": false,
"default": false,
"needs": [
"database_type=postgres,mysql"
]
}
]
},
{
"name": "ports",
"title": "Ports",
"items": [
{
"name": "ports_http",
"type": "str",
"title": "HTTP Port",
"required": false,
"default": 3000,
"config": {
"placeholder": "3000"
}
},
{
"name": "ports_ssh",
"type": "str",
"title": "Ports Ssh",
"required": false,
"default": 2221,
"config": {
"placeholder": "2221"
}
}
]
},
{
"name": "traefik",
"title": "Traefik",
"toggle": "traefik_enabled",
"items": [
{
"name": "traefik_enabled",
"type": "bool",
"title": "Traefik",
"required": false,
"default": false
},
{
"name": "traefik_host",
"type": "str",
"title": "Host",
"required": true,
"config": {
"placeholder": "gitea"
}
},
{
"name": "traefik_domain",
"type": "str",
"title": "Domain",
"required": true,
"config": {
"placeholder": "home.arpa"
}
},
{
"name": "traefik_entrypoint",
"type": "str",
"title": "Entrypoint",
"required": true,
"default": "web",
"config": {
"placeholder": "web"
}
},
{
"name": "traefik_network",
"type": "str",
"title": "Network",
"required": true,
"default": "traefik",
"config": {
"placeholder": "traefik"
}
},
{
"name": "traefik_tls_enabled",
"type": "bool",
"title": "TLS",
"required": false,
"default": false
},
{
"name": "traefik_tls_entrypoint",
"type": "str",
"title": "TLS Entrypoint",
"required": true,
"default": "websecure",
"config": {
"placeholder": "websecure"
},
"needs": [
"traefik_tls_enabled=true"
]
},
{
"name": "traefik_tls_certresolver",
"type": "str",
"title": "Cert Resolver",
"required": true,
"default": "cloudflare",
"config": {
"placeholder": "cloudflare"
},
"needs": [
"traefik_tls_enabled=true"
]
}
]
}
]
}
@@ -1,8 +0,0 @@
services:
<< service_name >>:
image: docker.io/gitlab/gitlab-runner:alpine-v17.9.1
container_name: << container_name >>
volumes:
- ./config/config.toml:/etc/gitlab-runner/config.toml:ro
- /var/run/docker.sock:/var/run/docker.sock
restart: << restart_policy >>

Some files were not shown because too many files have changed in this diff Show More