Initialise repository structure and placeholder files. Generic functionality works i guess, need to come up with a better solution for compose templates

This commit is contained in:
boris committed 2026-07-08 10:29:51 +01:00
commit 53695f4fff
411 files changed
+7817

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
*.retry
.ansible/
.cache/
.vault-pass
*.log
*.pem
*.key
*.crt
*.csr
*.p12
*.env
secrets.yml
vault.yml
TODO.md
+278
View File
@@ -0,0 +1,278 @@
# nbops-ansible
## nbops
#### Infrastructure as Code for notBoris' Homelab
nbops is the central operations repository for my hosted infrastructure, containing everything required to provision, configure, and maintain my virtual machines and services. The goal is to treat my infrastructure as code for reproducibility and automation from source control, while having minimal manual intervention.
## About
nbops-ansible contains the configuration management layer of the nbops platform.
Every virtual machine begins life as a standardised Debian 13 GenericCloud template, provisioned by Proxmox and bootstrapped with Cloud-Init. Once the operating system has completed its initial configuration, the host runs ansible-pull against this repository to configure itself according to its assigned role.
A freshly created virtual machine should require little more than a hostname, network configuration and SSH key before automatically configuring itself into a production-ready host.
# Design Philosophy
This repository follows a layered approach where each component has a clearly defined responsibility.
```mermaid
flowchart TD
A[GitHub: nbops-ansible] --> B[Proxmox VE]
B --> C[Debian 13 GenericCloud Template]
C --> D[Clone VM]
D --> E[Cloud-Init Bootstrap]
E --> F[ansible-pull]
F --> G[Apply Base Roles]
G --> H[Apply Service Roles]
H --> I[Docker Compose]
I --> J[Running Services]
```
## Layer Responsibilities
```mermaid
flowchart LR
P[Proxmox VE<br/>VM lifecycle<br/>storage<br/>networking]
D[Debian 13<br/>GenericCloud<br/>minimal OS]
C[Cloud-Init<br/>identity<br/>network<br/>bootstrap]
A[Ansible<br/>desired state<br/>roles<br/>configuration]
DC[Docker Compose<br/>service runtime]
S[Applications<br/>actual workloads]
P --> D --> C --> A --> DC --> S
```
# Repository Structure
.
├── inventories/
│ ├── production/
│ └── development/
│
├── playbooks/
│
├── roles/
│ ├── base/
│ ├── docker/
│ ├── ssh/
│ ├── ufw/
│ ├── mtls/
│ ├── monitoring/
│ ├── traefik/
│ ├── authentik/
│ ├── immich/
│ ├── gitea/
│ ├── arr/
│ ├── ocis/
│ └── pterodactyl/
│
├── group_vars/
├── host_vars/
├── templates/
├── files/
├── scripts/
└── docs/
As the infrastructure grows, additional functionality should be implemented as new roles rather than modifying existing ones wherever practical.
# Configuration Workflow
Cloud-Init is intentionally lightweight.
Its responsibility is limited to preparing the operating system by configuring:
* Hostname
* Networking
* DNS
* Users
* SSH
* Docker
* Firewall
* Internal CA trust
* Base packages
Once complete, Cloud-Init hands over to Ansible.
Ansible is responsible for configuring and maintaining the desired state of every machine for the remainder of its lifecycle.
# Roles
The repository is built around reusable Ansible roles; every server receives a common baseline before additional service-specific roles are applied.
## Core Roles
* base
* docker
* ssh
* ufw
* mtls
* monitoring
* updates
## Service Roles
* traefik
* authentik
* gitea
* Pelican
* etc..
This approach allows new services to inherit the same security, logging and operating system configuration while remaining independent from one another.
# Operating System Standard
Unless otherwise required, every virtual machine deployed by nbops follows the same baseline.
* Debian 13 (Trixie)
* GenericCloud image
* UEFI (OVMF)
* Q35 machine type
* VirtIO networking
* VirtIO SCSI storage
* QEMU Guest Agent
* Docker Engine
* Docker Compose Plugin
* Chrony
* UFW
* SSH key authentication only
Standardising the operating system significantly reduces maintenance overhead and configuration drift across the environment.
# Infrastructure Principles
The following principles guide every decision made within this repository.
* Everything should be reproducible.
* Everything should be version controlled.
* Manual configuration should be avoided.
* Infrastructure should be disposable rather than unique.
* Security should be built into the baseline.
* Applications should remain isolated from the operating system wherever possible.
* Configuration should be modular and reusable.
# Future Goals
Planned work includes:
* Automated Debian template creation
* Cloud-Init template generation
* Automated certificate management
* Secret management
* Monitoring and alerting
* Backup automation
* Configuration validation
* Disaster recovery testing
* Multi-node infrastructure support
# Contributing
This repository is primarily maintained for my own homelab, but the techniques and playbooks may be useful to others building similar self-hosted environments.
Suggestions, improvements and ideas are always welcome.
# License
Unless otherwise stated, all code within this repository is released under the MIT License.
# nbops-ansible
Ansible repository for a reproducible Proxmox-based Infrastructure as Code
platform using Debian 13 GenericCloud, Cloud-Init, Docker Engine, Docker Compose,
and `ansible-pull`.
## Model
```text
Proxmox
-> Debian 13 GenericCloud template
-> clone
-> Cloud-Init
-> ansible-pull
-> base roles
-> service roles
-> Docker Compose
```
Cloud-Init handles Day 0 bootstrap only. Ansible owns Day 1 onward state.
## Layout
- `inventories/`: production and development inventories
- `playbooks/`: site and pull entrypoints
- `roles/`: reusable core and application roles
- `group_vars/`: shared defaults
- `host_vars/`: per-VM overrides
- `templates/`: Cloud-Init and shared templates
- `scripts/`: local helper scripts
- `docs/`: operational notes
## Roles
Core roles:
- `base`
- `ssh`
- `ufw`
- `updates`
- `docker`
- `mtls`
- `monitoring`
Application roles:
- `traefik`
- `authentik`
- `immich`
- `gitea`
- `ocis`
- `pterodactyl`
- `arr`
Enable roles per host with `nbops_roles`.
```yaml
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
- traefik
```
## VM Topology
Every VM is expected to run Docker. Put `docker` in `nbops_roles` for each VM.
The edge VM normally runs `traefik` and `authentik`, with Traefik publishing
ports `80` and `443`. Downstream VMs can run their own internal Traefik and set
`nbops_service_routing_mode: traefik` so applications register with Traefik
instead of exposing individual host ports.
See `host_vars/edge.yml.example`, `host_vars/downstream.yml.example`, and
`docs/topology.md`. The full Docker service list is in `docs/service-catalog.md`.
## Bootstrap
Render `templates/cloud-init-user-data.yml.j2` with host variables and pass it to
Proxmox as user-data. The template installs baseline packages, Docker from
Docker's official Debian repository, configures UFW and SSH, creates `/srv` and
PKI directories, and executes `ansible-pull`.
## Secrets
Secrets are intentionally excluded. Do not commit passwords, API keys,
certificates, private keys, or generated `.env` files. Use Ansible Vault, SOPS,
Vault, or Smallstep-backed issuance when introducing real secret material.
## Validation
```sh
ansible-galaxy collection install -r requirements.yml
scripts/check.sh
```
+22
View File
@@ -0,0 +1,22 @@
[defaults]
inventory = inventories/production/hosts.yml
roles_path = roles
collections_path = .ansible/collections:~/.ansible/collections:/usr/share/ansible/collections
stdout_callback = yaml
bin_ansible_callbacks = True
host_key_checking = True
retry_files_enabled = False
interpreter_python = auto_silent
fact_caching = jsonfile
fact_caching_connection = .ansible/facts
fact_caching_timeout = 86400
[privilege_escalation]
become = True
become_method = sudo
become_ask_pass = False
[ssh_connection]
pipelining = True
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
+32
View File
@@ -0,0 +1,32 @@
# Operations
Install collections:
```sh
ansible-galaxy collection install -r requirements.yml
```
Run a syntax check:
```sh
ansible-playbook --syntax-check playbooks/site.yml
```
Run locally:
```sh
ansible-playbook -i inventories/development/hosts.yml playbooks/site.yml
```
Run selected tags:
```sh
ansible-playbook -i inventories/production/hosts.yml playbooks/site.yml --tags base,docker,traefik
```
Run with ansible-pull:
```sh
REPO_URL=git@github.com:example/nbops-ansible.git scripts/ansible-pull.sh
```
+20
View File
@@ -0,0 +1,20 @@
# Proxmox Debian 13 GenericCloud Template
Expected VM defaults:
- Debian 13 Trixie GenericCloud image
- Q35 machine type
- UEFI / OVMF
- VirtIO SCSI controller
- VirtIO network device
- CPU type `host`
- QEMU Guest Agent enabled
- Cloud-Init drive attached
- 2 vCPU, 2 GB RAM, 32 GB disk unless a service needs more
Cloud-Init is only responsible for Day 0 bootstrap: user creation, SSH
hardening, Docker installation, baseline packages, firewall defaults, directory
layout, PKI directory preparation, and `ansible-pull`.
Applications and durable service configuration belong in Ansible roles.
+16
View File
@@ -0,0 +1,16 @@
# Secrets
Do not commit passwords, API keys, issued certificates, private keys, or `.env`
files containing secrets.
This repository is ready for these future integrations:
- Ansible Vault
- SOPS
- HashiCorp Vault
- Smallstep certificates
Role defaults deliberately use `CHANGE_ME_IN_VAULT` placeholders for required
application secrets. Override those values in encrypted inventory data before
deploying real services.
+63
View File
@@ -0,0 +1,63 @@
# Service Catalog
These Docker service roles are available through `nbops_roles`.
Media and downloads:
- `jellyfin`
- `jellyseerr`
- `qbittorrent`
- `transmission`
- `wizarr`
- `flaresolverr`
- `cleanuparr`
- `pinchflat`
- `kavita`
Identity and access:
- `authentik`
- `authentik_ldap`
- `authentik_proxy`
- `authentik_rac`
- `vaultwarden`
Dashboards and file tools:
- `homarr`
- `nginx_content`
- `code_server`
- `filebrowser_quantum`
- `kiwix_serve`
- `requarks`
- `fiveetools`
- `ntfy`
Infrastructure:
- `pelican_panel`
- `pelican_server`
- `gitea`
- `gitea_act_runner`
- `postgres`
- `php_fpm`
- `valkey`
- `syncthing`
- `ipv6nat`
Monitoring:
- `grafana`
- `loki`
- `alloy`
- `grafana_renderer`
- `prometheus`
- `uptime_kuma`
Most roles use the shared `docker_app` implementation and support:
- `host` routing mode: publish ports directly and open UFW rules.
- `traefik` routing mode: attach to the proxy network and render Traefik labels.
Override role defaults in `host_vars` or `group_vars`; do not edit generated
Compose files on the VM.
+59
View File
@@ -0,0 +1,59 @@
# Topology
Every VM is a Docker host. Proxmox and Cloud-Init only create the machine and
bootstrap Ansible; service placement is controlled by inventory.
## Edge VM
The edge VM runs:
- Docker
- Traefik
- authentik IAM
It publishes Traefik entrypoints on host ports `80` and `443`. authentik can run
behind the same local Traefik instance by setting:
```yaml
nbops_service_routing_mode: traefik
authentik_domain: auth.example.invalid
```
## Downstream VMs
Downstream VMs also run Docker. Instead of publishing each application port on
the host, run Traefik on the VM and attach application containers to the shared
Docker proxy network.
```yaml
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- traefik
- gitea
- immich
nbops_service_routing_mode: traefik
nbops_traefik_entrypoint: web
nbops_traefik_tls: false
```
In this mode application roles do not publish their HTTP ports. They render
Traefik labels and join `nbops_proxy_network`, which defaults to `proxy`.
Only Traefik entrypoints should be exposed on the downstream host firewall. Use
`traefik_bind_address` to bind those entrypoints to the VM's internal address.
## Routing Modes
Application roles support:
- `host`: publish application ports directly and open those ports in UFW.
- `traefik`: do not publish HTTP ports; attach to Traefik and render labels.
Gitea is special: HTTP can be routed through Traefik while SSH remains published
with `gitea_publish_ssh: true`.
+1
View File
@@ -0,0 +1 @@
+9
View File
@@ -0,0 +1,9 @@
# Group Variables
Group variables define shared defaults for every VM. Override them in environment
or host-specific files rather than editing role tasks.
Secrets, private keys, API tokens, and issued certificates must not be committed.
Use Ansible Vault, SOPS, Vault, or Smallstep-backed workflows when those values
are introduced.
+71
View File
@@ -0,0 +1,71 @@
---
nbops_environment: production
nbops_admin_user: admin
nbops_admin_groups:
- sudo
nbops_hostname: "{{ inventory_hostname }}"
nbops_fqdn: "{{ inventory_hostname }}"
nbops_timezone: Etc/UTC
nbops_locale: en_GB.UTF-8
nbops_ssh_port: 22
nbops_dns_servers:
- 1.1.1.1
- 9.9.9.9
nbops_standard_directories:
- /srv/docker
- /srv/config
- /srv/backups
- /srv/data
- /srv/media
- /etc/pki/internal
- /etc/pki/internal/private
- /etc/pki/internal/certs
- /usr/local/share/ca-certificates/internal
nbops_base_packages:
- acl
- apt-transport-https
- bash-completion
- ca-certificates
- chrony
- curl
- git
- gnupg
- htop
- jq
- locales
- lsb-release
- needrestart
- qemu-guest-agent
- rsync
- sudo
- ufw
- unattended-upgrades
- vim
nbops_sysctl:
net.ipv4.ip_forward: "1"
net.ipv6.conf.all.forwarding: "1"
fs.inotify.max_user_watches: "1048576"
fs.inotify.max_user_instances: "1024"
docker_users:
- "{{ nbops_admin_user }}"
ufw_allowed_tcp_ports:
- "{{ nbops_ssh_port }}"
ufw_allowed_udp_ports: []
monitoring_agent: none
monitoring_packages: []
mtls_ca_certificates: []
mtls_host_certificates: []
mtls_private_keys: []
nbops_proxy_network: proxy
nbops_service_routing_mode: host
nbops_service_domain_suffix: example.invalid
nbops_traefik_entrypoint: websecure
nbops_traefik_tls: true
+7
View File
@@ -0,0 +1,7 @@
# Host Variables
Create one file per VM, for example `host_vars/media-01.yml`.
Use host variables for hostnames, service enablement, storage paths, and port
assignments. Keep credentials and TLS private material outside Git.
+29
View File
@@ -0,0 +1,29 @@
---
nbops_hostname: cloud
nbops_fqdn: cloud.example.internal
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
- traefik
- ocis
nbops_service_routing_mode: traefik
nbops_traefik_entrypoint: web
nbops_traefik_tls: false
# Bind this to the VM's internal/LAN address in production.
traefik_bind_address: "0.0.0.0"
traefik_publish_entrypoints: true
traefik_domain: apps-01.example.internal
traefik_forwarded_headers_trusted_ips:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ocis_url: https://files.example.internal
+31
View File
@@ -0,0 +1,31 @@
---
nbops_hostname: edge
nbops_fqdn: edge.example.invalid
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
- traefik
- authentik
- authentik_rac
- authentik_ldap
- authentik_worker
nbops_service_routing_mode: traefik
nbops_service_domain_suffix: example.invalid
nbops_traefik_entrypoint: websecure
nbops_traefik_tls: true
traefik_bind_address: "0.0.0.0"
traefik_publish_entrypoints: true
traefik_domain: example.invalid
traefik_acme_email: admin@example.invalid
authentik_domain: auth.example.invalid
authentik_http_port: 9000
authentik_https_port: 9443
+57
View File
@@ -0,0 +1,57 @@
---
nbops_hostname: apps-01
nbops_fqdn: apps-01.example.internal
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
- traefik
- gitea
- immich
- ocis
- arr
nbops_service_routing_mode: traefik
nbops_traefik_entrypoint: web
nbops_traefik_tls: false
# Bind this to the VM's internal/LAN address in production.
traefik_bind_address: "0.0.0.0"
traefik_publish_entrypoints: true
traefik_domain: apps-01.example.internal
traefik_forwarded_headers_trusted_ips:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
gitea_domain: git.example.internal
gitea_publish_ssh: true
immich_domain: photos.example.internal
ocis_url: https://files.example.internal
arr_services:
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
port: 9696
domain: prowlarr.example.internal
sonarr:
image: lscr.io/linuxserver/sonarr:latest
port: 8989
domain: sonarr.example.internal
radarr:
image: lscr.io/linuxserver/radarr:latest
port: 7878
domain: radarr.example.internal
lidarr:
image: lscr.io/linuxserver/lidarr:latest
port: 8686
domain: lidarr.example.internal
bazarr:
image: lscr.io/linuxserver/bazarr:latest
port: 6767
domain: bazarr.example.internal
+15
View File
@@ -0,0 +1,15 @@
---
nbops_hostname: pelican-node-01
nbops_fqdn: pelican-node-01.example.internal
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- pelican_server
pelican_server_daemon_port: 8080
pelican_server_sftp_port: 2022
pelican_server_allocation_ports:
- { port: "25565:25575", proto: tcp }
+29
View File
@@ -0,0 +1,29 @@
---
nbops_hostname: cloud
nbops_fqdn: cloud.example.internal
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
- traefik
- ocis
nbops_service_routing_mode: traefik
nbops_traefik_entrypoint: web
nbops_traefik_tls: false
# Bind this to the VM's internal/LAN address in production.
traefik_bind_address: "0.0.0.0"
traefik_publish_entrypoints: true
traefik_domain: apps-01.example.internal
traefik_forwarded_headers_trusted_ips:
- 10.0.0.0/8
- 172.16.0.0/12
- 192.168.0.0/16
ocis_url: https://files.example.internal
+16
View File
@@ -0,0 +1,16 @@
---
all:
children:
nbops:
hosts:
localhost:
ansible_connection: local
ansible_python_interpreter: /usr/bin/python3
nbops_environment: development
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
+17
View File
@@ -0,0 +1,17 @@
---
all:
children:
nbops:
hosts:
localhost:
ansible_connection: local
ansible_python_interpreter: /usr/bin/python3
nbops_roles:
- base
- ssh
- ufw
- updates
- docker
- mtls
- monitoring
+4
View File
@@ -0,0 +1,4 @@
---
- name: Local ansible-pull entrypoint
ansible.builtin.import_playbook: site.yml
+167
View File
@@ -0,0 +1,167 @@
---
- name: Converge nbops host
hosts: all
become: true
gather_facts: true
pre_tasks:
- name: Validate supported operating system
ansible.builtin.assert:
that:
- ansible_distribution == "Debian"
- ansible_distribution_major_version | int >= 13
fail_msg: "nbops targets Debian 13 Trixie or newer."
tags:
- always
roles:
- role: base
tags: [base]
when: "'base' in nbops_roles | default([])"
- role: ssh
tags: [ssh]
when: "'ssh' in nbops_roles | default([])"
- role: ufw
tags: [ufw]
when: "'ufw' in nbops_roles | default([])"
- role: updates
tags: [updates]
when: "'updates' in nbops_roles | default([])"
- role: docker
tags: [docker]
when: "'docker' in nbops_roles | default([])"
- role: mtls
tags: [mtls]
when: "'mtls' in nbops_roles | default([])"
- role: monitoring
tags: [monitoring]
when: "'monitoring' in nbops_roles | default([])"
- role: traefik
tags: [traefik]
when: "'traefik' in nbops_roles | default([])"
- role: authentik
tags: [authentik]
when: "'authentik' in nbops_roles | default([])"
- role: immich
tags: [immich]
when: "'immich' in nbops_roles | default([])"
- role: gitea
tags: [gitea]
when: "'gitea' in nbops_roles | default([])"
- role: ocis
tags: [ocis]
when: "'ocis' in nbops_roles | default([])"
- role: pterodactyl
tags: [pterodactyl]
when: "'pterodactyl' in nbops_roles | default([])"
- role: arr
tags: [arr]
when: "'arr' in nbops_roles | default([])"
- role: jellyfin
tags: [jellyfin]
when: "'jellyfin' in nbops_roles | default([])"
- role: pelican_panel
tags: [pelican_panel]
when: "'pelican_panel' in nbops_roles | default([])"
- role: pelican_server
tags: [pelican_server]
when: "'pelican_server' in nbops_roles | default([])"
- role: authentik_ldap
tags: [authentik_ldap]
when: "'authentik_ldap' in nbops_roles | default([])"
- role: authentik_proxy
tags: [authentik_proxy]
when: "'authentik_proxy' in nbops_roles | default([])"
- role: authentik_rac
tags: [authentik_rac]
when: "'authentik_rac' in nbops_roles | default([])"
- role: jellyseerr
tags: [jellyseerr]
when: "'jellyseerr' in nbops_roles | default([])"
- role: qbittorrent
tags: [qbittorrent]
when: "'qbittorrent' in nbops_roles | default([])"
- role: transmission
tags: [transmission]
when: "'transmission' in nbops_roles | default([])"
- role: wizarr
tags: [wizarr]
when: "'wizarr' in nbops_roles | default([])"
- role: homarr
tags: [homarr]
when: "'homarr' in nbops_roles | default([])"
- role: flaresolverr
tags: [flaresolverr]
when: "'flaresolverr' in nbops_roles | default([])"
- role: cleanuparr
tags: [cleanuparr]
when: "'cleanuparr' in nbops_roles | default([])"
- role: pinchflat
tags: [pinchflat]
when: "'pinchflat' in nbops_roles | default([])"
- role: nginx_content
tags: [nginx_content]
when: "'nginx_content' in nbops_roles | default([])"
- role: code_server
tags: [code_server]
when: "'code_server' in nbops_roles | default([])"
- role: filebrowser_quantum
tags: [filebrowser_quantum]
when: "'filebrowser_quantum' in nbops_roles | default([])"
- role: kiwix_serve
tags: [kiwix_serve]
when: "'kiwix_serve' in nbops_roles | default([])"
- role: ipv6nat
tags: [ipv6nat]
when: "'ipv6nat' in nbops_roles | default([])"
- role: php_fpm
tags: [php_fpm]
when: "'php_fpm' in nbops_roles | default([])"
- role: valkey
tags: [valkey]
when: "'valkey' in nbops_roles | default([])"
- role: syncthing
tags: [syncthing]
when: "'syncthing' in nbops_roles | default([])"
- role: paperless_ngx
tags: [paperless_ngx]
when: "'paperless_ngx' in nbops_roles | default([])"
- role: kavita
tags: [kavita]
when: "'kavita' in nbops_roles | default([])"
- role: gitea_act_runner
tags: [gitea_act_runner]
when: "'gitea_act_runner' in nbops_roles | default([])"
- role: vaultwarden
tags: [vaultwarden]
when: "'vaultwarden' in nbops_roles | default([])"
- role: grafana
tags: [grafana]
when: "'grafana' in nbops_roles | default([])"
- role: loki
tags: [loki]
when: "'loki' in nbops_roles | default([])"
- role: alloy
tags: [alloy]
when: "'alloy' in nbops_roles | default([])"
- role: grafana_renderer
tags: [grafana_renderer]
when: "'grafana_renderer' in nbops_roles | default([])"
- role: prometheus
tags: [prometheus]
when: "'prometheus' in nbops_roles | default([])"
- role: uptime_kuma
tags: [uptime_kuma]
when: "'uptime_kuma' in nbops_roles | default([])"
- role: requarks
tags: [requarks]
when: "'requarks' in nbops_roles | default([])"
- role: postgres
tags: [postgres]
when: "'postgres' in nbops_roles | default([])"
- role: fiveetools
tags: [fiveetools]
when: "'fiveetools' in nbops_roles | default([])"
- role: ntfy
tags: [ntfy]
when: "'ntfy' in nbops_roles | default([])"
+6
View File
@@ -0,0 +1,6 @@
---
collections:
- name: ansible.posix
- name: community.docker
- name: community.general
+4
View File
@@ -0,0 +1,4 @@
# alloy
Deploys Grafana Alloy with Docker Compose.
+17
View File
@@ -0,0 +1,17 @@
---
alloy_name: alloy
alloy_dir: /srv/docker/alloy
alloy_image: grafana/alloy:latest
alloy_routing_mode: host
alloy_command: "run --server.http.listen-addr=0.0.0.0:12345 /etc/alloy/config.alloy"
alloy_env: {}
alloy_directories:
- "{{ alloy_dir }}"
- /srv/config/alloy
alloy_volumes:
- /srv/config/alloy:/etc/alloy
- /var/log:/var/log:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
alloy_ports:
- { host: 12345, container: 12345, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+17
View File
@@ -0,0 +1,17 @@
---
- name: Deploy Grafana Alloy
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/alloy/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/alloy/templates/compose.yaml.j2"
docker_app_name: "{{ alloy_name }}"
docker_app_dir: "{{ alloy_dir }}"
docker_app_image: "{{ alloy_image }}"
docker_app_command: "{{ alloy_command }}"
docker_app_env: "{{ alloy_env }}"
docker_app_directories: "{{ alloy_directories }}"
docker_app_volumes: "{{ alloy_volumes }}"
docker_app_ports: "{{ alloy_ports }}"
docker_app_routing_mode: "{{ alloy_routing_mode }}"
+1
View File
@@ -0,0 +1 @@
+90
View File
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+7
View File
@@ -0,0 +1,7 @@
# arr
Deploys a LinuxServer.io Arr stack with Prowlarr, Sonarr, Radarr, Lidarr, and
Bazarr. Override `arr_services` to add or remove services.
Tags: `arr`, `config`, `compose`, `firewall`.
+34
View File
@@ -0,0 +1,34 @@
---
arr_dir: /srv/docker/arr
arr_puid: "1000"
arr_pgid: "1000"
arr_timezone: "{{ nbops_timezone }}"
arr_config_root: /srv/config/arr
arr_media_root: /srv/media
arr_downloads_root: /srv/data/downloads
arr_routing_mode: "{{ nbops_service_routing_mode }}"
arr_traefik_network: "{{ nbops_proxy_network }}"
arr_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}"
arr_traefik_tls: "{{ nbops_traefik_tls }}"
arr_services:
prowlarr:
image: lscr.io/linuxserver/prowlarr:latest
port: 9696
domain: prowlarr.example.invalid
sonarr:
image: lscr.io/linuxserver/sonarr:latest
port: 8989
domain: sonarr.example.invalid
radarr:
image: lscr.io/linuxserver/radarr:latest
port: 7878
domain: radarr.example.invalid
lidarr:
image: lscr.io/linuxserver/lidarr:latest
port: 8686
domain: lidarr.example.invalid
bazarr:
image: lscr.io/linuxserver/bazarr:latest
port: 6767
domain: bazarr.example.invalid
arr_tcp_ports: "{{ arr_services | dict2items | map(attribute='value.port') | list }}"
+1
View File
@@ -0,0 +1 @@
+6
View File
@@ -0,0 +1,6 @@
---
- name: restart arr
community.docker.docker_compose_v2:
project_src: "{{ arr_dir }}"
state: restarted
+65
View File
@@ -0,0 +1,65 @@
---
- name: Create Arr stack directories
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "0755"
loop:
- "{{ arr_dir }}"
- "{{ arr_config_root }}"
- "{{ arr_media_root }}"
- "{{ arr_downloads_root }}"
tags: [arr]
- name: Create Arr application config directories
ansible.builtin.file:
path: "{{ arr_config_root }}/{{ item.key }}"
state: directory
owner: root
group: root
mode: "0755"
loop: "{{ arr_services | dict2items }}"
tags: [arr, config]
- name: Render Arr environment
ansible.builtin.template:
src: env.j2
dest: "{{ arr_dir }}/.env"
owner: root
group: root
mode: "0644"
tags: [arr, config]
- name: Ensure Arr proxy network exists
community.docker.docker_network:
name: "{{ arr_traefik_network }}"
state: present
when: arr_routing_mode == "traefik"
tags: [arr, docker]
- name: Render Arr Compose file
ansible.builtin.template:
src: compose.yaml.j2
dest: "{{ arr_dir }}/compose.yaml"
owner: root
group: root
mode: "0644"
notify: restart arr
tags: [arr, compose]
- name: Allow Arr ports
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ arr_tcp_ports }}"
when: arr_routing_mode == "host"
tags: [arr, firewall]
- name: Start Arr stack
community.docker.docker_compose_v2:
project_src: "{{ arr_dir }}"
state: present
tags: [arr, compose]
+31
View File
@@ -0,0 +1,31 @@
services:
{% for name, service in arr_services.items() %}
{{ name }}:
image: {{ service.image }}
restart: unless-stopped
env_file: .env
{% if arr_routing_mode == "host" %}
ports:
- "{{ service.port }}:{{ service.port }}"
{% endif %}
{% if arr_routing_mode == "traefik" %}
# internal service port: {{ service.port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ name }}.rule=Host(`{{ service.domain }}`)
- traefik.http.routers.{{ name }}.entrypoints={{ arr_traefik_entrypoint }}
- traefik.http.routers.{{ name }}.tls={{ arr_traefik_tls | bool | ternary('true', 'false') }}
- traefik.http.services.{{ name }}.loadbalancer.server.port={{ service.port }}
networks:
- {{ arr_traefik_network }}
{% endif %}
volumes:
- {{ arr_config_root }}/{{ name }}:/config
- {{ arr_media_root }}:/media
- {{ arr_downloads_root }}:/downloads
{% endfor %}
{% if arr_routing_mode == "traefik" %}
networks:
{{ arr_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
PUID={{ arr_puid }}
PGID={{ arr_pgid }}
TZ={{ arr_timezone }}
+7
View File
@@ -0,0 +1,7 @@
# authentik
Deploys authentik with PostgreSQL and Redis. Override secret values from Vault,
SOPS, or Ansible Vault before production use.
Tags: `authentik`, `config`, `compose`, `firewall`.
+24
View File
@@ -0,0 +1,24 @@
---
authentik_dir: /srv/docker/authentik
authentik_image: ghcr.io/goauthentik/server:2025.6
authentik_postgres_image: docker.io/library/postgres:16-alpine
authentik_redis_image: docker.io/library/redis:7-alpine
authentik_http_port: 9000
authentik_https_port: 9443
authentik_domain: auth.example.invalid
authentik_routing_mode: "{{ nbops_service_routing_mode }}"
authentik_traefik_network: "{{ nbops_proxy_network }}"
authentik_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}"
authentik_traefik_tls: "{{ nbops_traefik_tls }}"
authentik_secret_key: "{{ vault_authentik_secret_key | default('CHANGE_ME_IN_VAULT') }}"
authentik_postgres_password: "{{ vault_authentik_postgres_password | default('CHANGE_ME_IN_VAULT') }}"
authentik_env:
AUTHENTIK_SECRET_KEY: "{{ authentik_secret_key }}"
AUTHENTIK_POSTGRESQL__HOST: postgresql
AUTHENTIK_POSTGRESQL__USER: authentik
AUTHENTIK_POSTGRESQL__NAME: authentik
AUTHENTIK_POSTGRESQL__PASSWORD: "{{ authentik_postgres_password }}"
AUTHENTIK_REDIS__HOST: redis
authentik_tcp_ports:
- "{{ authentik_http_port }}"
- "{{ authentik_https_port }}"
+1
View File
@@ -0,0 +1 @@
+6
View File
@@ -0,0 +1,6 @@
---
- name: restart authentik
community.docker.docker_compose_v2:
project_src: "{{ authentik_dir }}"
state: restarted
+51
View File
@@ -0,0 +1,51 @@
---
- name: Create authentik directory
ansible.builtin.file:
path: "{{ authentik_dir }}"
state: directory
owner: root
group: root
mode: "0755"
tags: [authentik]
- name: Render authentik environment
ansible.builtin.template:
src: env.j2
dest: "{{ authentik_dir }}/.env"
owner: root
group: root
mode: "0600"
no_log: true
tags: [authentik, config]
- name: Ensure authentik proxy network exists
community.docker.docker_network:
name: "{{ authentik_traefik_network }}"
state: present
when: authentik_routing_mode == "traefik"
tags: [authentik, docker]
- name: Render authentik Compose file
ansible.builtin.template:
src: compose.yaml.j2
dest: "{{ authentik_dir }}/compose.yaml"
owner: root
group: root
mode: "0644"
notify: restart authentik
tags: [authentik, compose]
- name: Allow authentik ports
community.general.ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop: "{{ authentik_tcp_ports }}"
when: authentik_routing_mode == "host"
tags: [authentik, firewall]
- name: Start authentik
community.docker.docker_compose_v2:
project_src: "{{ authentik_dir }}"
state: present
tags: [authentik, compose]
+51
View File
@@ -0,0 +1,51 @@
services:
postgresql:
image: {{ authentik_postgres_image }}
restart: unless-stopped
environment:
POSTGRES_DB: authentik
POSTGRES_USER: authentik
POSTGRES_PASSWORD: ${PG_PASS}
volumes:
- ./postgresql:/var/lib/postgresql/data
redis:
image: {{ authentik_redis_image }}
restart: unless-stopped
server:
image: {{ authentik_image }}
restart: unless-stopped
command: server
env_file: .env
{% if authentik_routing_mode == "host" %}
ports:
- "{{ authentik_http_port }}:9000"
- "{{ authentik_https_port }}:9443"
{% endif %}
{% if authentik_routing_mode == "traefik" %}
# internal HTTP service port: 9000; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.authentik.rule=Host(`{{ authentik_domain }}`)
- traefik.http.routers.authentik.entrypoints={{ authentik_traefik_entrypoint }}
- traefik.http.routers.authentik.tls={{ authentik_traefik_tls | bool | ternary('true', 'false') }}
- traefik.http.services.authentik.loadbalancer.server.port=9000
networks:
- default
- {{ authentik_traefik_network }}
{% endif %}
depends_on:
- postgresql
- redis
worker:
image: {{ authentik_image }}
restart: unless-stopped
command: worker
env_file: .env
depends_on:
- postgresql
- redis
{% if authentik_routing_mode == "traefik" %}
networks:
{{ authentik_traefik_network }}:
external: true
{% endif %}
+5
View File
@@ -0,0 +1,5 @@
PG_PASS={{ authentik_postgres_password }}
{% for key, value in authentik_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+4
View File
@@ -0,0 +1,4 @@
# authentik_ldap
Deploys an authentik LDAP outpost.
+15
View File
@@ -0,0 +1,15 @@
---
authentik_ldap_name: authentik-ldap
authentik_ldap_dir: /srv/docker/authentik-ldap
authentik_ldap_image: ghcr.io/goauthentik/ldap:2025.6
authentik_ldap_routing_mode: host
authentik_ldap_env:
AUTHENTIK_HOST: https://auth.example.invalid
AUTHENTIK_TOKEN: "{{ vault_authentik_ldap_token | default('CHANGE_ME_IN_VAULT') }}"
authentik_ldap_directories:
- "{{ authentik_ldap_dir }}"
authentik_ldap_volumes: []
authentik_ldap_ports:
- { host: 3389, container: 3389, protocol: tcp }
- { host: 6636, container: 6636, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+16
View File
@@ -0,0 +1,16 @@
---
- name: Deploy authentik LDAP outpost
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_ldap/templates/compose.yaml.j2"
docker_app_name: "{{ authentik_ldap_name }}"
docker_app_dir: "{{ authentik_ldap_dir }}"
docker_app_image: "{{ authentik_ldap_image }}"
docker_app_env: "{{ authentik_ldap_env }}"
docker_app_directories: "{{ authentik_ldap_directories }}"
docker_app_volumes: "{{ authentik_ldap_volumes }}"
docker_app_ports: "{{ authentik_ldap_ports }}"
docker_app_routing_mode: "{{ authentik_ldap_routing_mode }}"
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+4
View File
@@ -0,0 +1,4 @@
# authentik_proxy
Deploys an authentik proxy outpost.
+17
View File
@@ -0,0 +1,17 @@
---
authentik_proxy_name: authentik-proxy
authentik_proxy_dir: /srv/docker/authentik-proxy
authentik_proxy_image: ghcr.io/goauthentik/proxy:2025.6
authentik_proxy_domain: proxy.example.invalid
authentik_proxy_service_port: 9000
authentik_proxy_routing_mode: "{{ nbops_service_routing_mode }}"
authentik_proxy_env:
AUTHENTIK_HOST: https://auth.example.invalid
AUTHENTIK_TOKEN: "{{ vault_authentik_proxy_token | default('CHANGE_ME_IN_VAULT') }}"
authentik_proxy_directories:
- "{{ authentik_proxy_dir }}"
authentik_proxy_volumes: []
authentik_proxy_ports:
- { host: 9001, container: 9000, protocol: tcp }
- { host: 9444, container: 9443, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+18
View File
@@ -0,0 +1,18 @@
---
- name: Deploy authentik proxy outpost
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_proxy/templates/compose.yaml.j2"
docker_app_name: "{{ authentik_proxy_name }}"
docker_app_dir: "{{ authentik_proxy_dir }}"
docker_app_image: "{{ authentik_proxy_image }}"
docker_app_env: "{{ authentik_proxy_env }}"
docker_app_directories: "{{ authentik_proxy_directories }}"
docker_app_volumes: "{{ authentik_proxy_volumes }}"
docker_app_ports: "{{ authentik_proxy_ports }}"
docker_app_routing_mode: "{{ authentik_proxy_routing_mode }}"
docker_app_domain: "{{ authentik_proxy_domain }}"
docker_app_service_port: "{{ authentik_proxy_service_port }}"
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+4
View File
@@ -0,0 +1,4 @@
# authentik_rac
Deploys an authentik RAC outpost.
+16
View File
@@ -0,0 +1,16 @@
---
authentik_rac_name: authentik-rac
authentik_rac_dir: /srv/docker/authentik-rac
authentik_rac_image: ghcr.io/goauthentik/rac:2025.6
authentik_rac_domain: rac.example.invalid
authentik_rac_service_port: 3389
authentik_rac_routing_mode: host
authentik_rac_env:
AUTHENTIK_HOST: https://auth.example.invalid
AUTHENTIK_TOKEN: "{{ vault_authentik_rac_token | default('CHANGE_ME_IN_VAULT') }}"
authentik_rac_directories:
- "{{ authentik_rac_dir }}"
authentik_rac_volumes: []
authentik_rac_ports:
- { host: 3390, container: 3389, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+18
View File
@@ -0,0 +1,18 @@
---
- name: Deploy authentik RAC outpost
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/authentik_rac/templates/compose.yaml.j2"
docker_app_name: "{{ authentik_rac_name }}"
docker_app_dir: "{{ authentik_rac_dir }}"
docker_app_image: "{{ authentik_rac_image }}"
docker_app_env: "{{ authentik_rac_env }}"
docker_app_directories: "{{ authentik_rac_directories }}"
docker_app_volumes: "{{ authentik_rac_volumes }}"
docker_app_ports: "{{ authentik_rac_ports }}"
docker_app_routing_mode: "{{ authentik_rac_routing_mode }}"
docker_app_domain: "{{ authentik_rac_domain }}"
docker_app_service_port: "{{ authentik_rac_service_port }}"
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+9
View File
@@ -0,0 +1,9 @@
# base
Configures the operating system baseline: hostname, locale, timezone, packages,
Chrony, QEMU guest agent, persistent journald, sysctl, aliases, PKI directories,
and `/srv` layout.
Tags: `base`, `hostname`, `packages`, `locale`, `timezone`, `directories`,
`journald`, `sysctl`, `shell`, `services`, `pki`.
+13
View File
@@ -0,0 +1,13 @@
---
base_hostname: "{{ nbops_hostname }}"
base_fqdn: "{{ nbops_fqdn }}"
base_timezone: "{{ nbops_timezone }}"
base_locale: "{{ nbops_locale }}"
base_packages: "{{ nbops_base_packages }}"
base_standard_directories: "{{ nbops_standard_directories }}"
base_sysctl: "{{ nbops_sysctl }}"
base_aliases:
ll: "ls -alF"
la: "ls -A"
l: "ls -CF"
+1
View File
@@ -0,0 +1 @@
+16
View File
@@ -0,0 +1,16 @@
---
- name: restart chrony
ansible.builtin.service:
name: chrony
state: restarted
- name: restart qemu guest agent
ansible.builtin.service:
name: qemu-guest-agent
state: restarted
- name: restart systemd-journald
ansible.builtin.service:
name: systemd-journald
state: restarted
+104
View File
@@ -0,0 +1,104 @@
---
- name: Set hostname
ansible.builtin.hostname:
name: "{{ base_hostname }}"
tags: [base, hostname]
- name: Configure hosts file
ansible.builtin.template:
src: hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: "0644"
tags: [base, hostname]
- name: Install base packages
ansible.builtin.apt:
name: "{{ base_packages }}"
state: present
update_cache: true
cache_valid_time: 3600
tags: [base, packages]
- name: Configure locale
community.general.locale_gen:
name: "{{ base_locale }}"
state: present
tags: [base, locale]
- name: Set system locale
ansible.builtin.copy:
dest: /etc/default/locale
owner: root
group: root
mode: "0644"
content: |
LANG={{ base_locale }}
LC_ALL={{ base_locale }}
tags: [base, locale]
- name: Configure timezone
community.general.timezone:
name: "{{ base_timezone }}"
notify: restart chrony
tags: [base, timezone]
- name: Ensure standard directories exist
ansible.builtin.file:
path: "{{ item }}"
state: directory
owner: root
group: root
mode: "{{ '0700' if item.endswith('/private') else '0755' }}"
loop: "{{ base_standard_directories }}"
tags: [base, directories]
- name: Configure persistent journald storage
ansible.builtin.copy:
dest: /etc/systemd/journald.conf.d/10-nbops.conf
owner: root
group: root
mode: "0644"
content: |
[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=1G
notify: restart systemd-journald
tags: [base, journald]
- name: Configure sysctl
ansible.posix.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
reload: true
loop: "{{ base_sysctl | dict2items }}"
tags: [base, sysctl]
- name: Configure shell aliases
ansible.builtin.template:
src: nbops-aliases.sh.j2
dest: /etc/profile.d/nbops-aliases.sh
owner: root
group: root
mode: "0644"
tags: [base, shell]
- name: Enable base services
ansible.builtin.service:
name: "{{ item }}"
enabled: true
state: started
loop:
- chrony
- qemu-guest-agent
tags: [base, services]
- name: Refresh CA certificate trust
ansible.builtin.command:
cmd: update-ca-certificates
changed_when: false
tags: [base, pki]
+7
View File
@@ -0,0 +1,7 @@
127.0.0.1 localhost
127.0.1.1 {{ base_fqdn }} {{ base_hostname }}
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
+4
View File
@@ -0,0 +1,4 @@
{% for name, command in base_aliases.items() %}
alias {{ name }}='{{ command }}'
{% endfor %}
+4
View File
@@ -0,0 +1,4 @@
# cleanuparr
Deploys Cleanuparr with Docker Compose.
+18
View File
@@ -0,0 +1,18 @@
---
cleanuparr_name: cleanuparr
cleanuparr_dir: /srv/docker/cleanuparr
cleanuparr_image: ghcr.io/cleanuparr/cleanuparr:latest
cleanuparr_domain: cleanuparr.example.invalid
cleanuparr_service_port: 11011
cleanuparr_routing_mode: "{{ nbops_service_routing_mode }}"
cleanuparr_env:
TZ: "{{ nbops_timezone }}"
cleanuparr_directories:
- "{{ cleanuparr_dir }}"
- /srv/config/cleanuparr
cleanuparr_volumes:
- /srv/config/cleanuparr:/config
- /srv/data/downloads:/downloads
cleanuparr_ports:
- { host: 11011, container: 11011, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+18
View File
@@ -0,0 +1,18 @@
---
- name: Deploy Cleanuparr
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/cleanuparr/templates/compose.yaml.j2"
docker_app_name: "{{ cleanuparr_name }}"
docker_app_dir: "{{ cleanuparr_dir }}"
docker_app_image: "{{ cleanuparr_image }}"
docker_app_env: "{{ cleanuparr_env }}"
docker_app_directories: "{{ cleanuparr_directories }}"
docker_app_volumes: "{{ cleanuparr_volumes }}"
docker_app_ports: "{{ cleanuparr_ports }}"
docker_app_routing_mode: "{{ cleanuparr_routing_mode }}"
docker_app_domain: "{{ cleanuparr_domain }}"
docker_app_service_port: "{{ cleanuparr_service_port }}"
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+4
View File
@@ -0,0 +1,4 @@
# code_server
Deploys code-server with Docker Compose.
+22
View File
@@ -0,0 +1,22 @@
---
code_server_name: code-server
code_server_dir: /srv/docker/code-server
code_server_image: lscr.io/linuxserver/code-server:latest
code_server_domain: code.example.invalid
code_server_service_port: 8443
code_server_routing_mode: "{{ nbops_service_routing_mode }}"
code_server_env:
PUID: "1000"
PGID: "1000"
TZ: "{{ nbops_timezone }}"
PASSWORD: "{{ vault_code_server_password | default('CHANGE_ME_IN_VAULT') }}"
code_server_directories:
- "{{ code_server_dir }}"
- /srv/config/code-server
- /srv/data/workspace
code_server_volumes:
- /srv/config/code-server:/config
- /srv/data/workspace:/workspace
code_server_ports:
- { host: 8443, container: 8443, protocol: tcp }
+1
View File
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+18
View File
@@ -0,0 +1,18 @@
---
- name: Deploy code-server
ansible.builtin.include_role:
name: docker_app
vars:
docker_app_env_template_src: "{{ playbook_dir }}/../roles/code_server/templates/env.j2"
docker_app_compose_template_src: "{{ playbook_dir }}/../roles/code_server/templates/compose.yaml.j2"
docker_app_name: "{{ code_server_name }}"
docker_app_dir: "{{ code_server_dir }}"
docker_app_image: "{{ code_server_image }}"
docker_app_env: "{{ code_server_env }}"
docker_app_directories: "{{ code_server_directories }}"
docker_app_volumes: "{{ code_server_volumes }}"
docker_app_ports: "{{ code_server_ports }}"
docker_app_routing_mode: "{{ code_server_routing_mode }}"
docker_app_domain: "{{ code_server_domain }}"
docker_app_service_port: "{{ code_server_service_port }}"
+1
View File
@@ -0,0 +1 @@
@@ -0,0 +1,90 @@
services:
{{ docker_app_name }}:
image: {{ docker_app_image }}
restart: {{ docker_app_restart }}
{% if docker_app_command | length > 0 %}
command: {{ docker_app_command | to_json }}
{% endif %}
{% if docker_app_user | length > 0 %}
user: "{{ docker_app_user }}"
{% endif %}
{% if docker_app_network_mode | length > 0 %}
network_mode: {{ docker_app_network_mode }}
{% endif %}
{% if docker_app_privileged | bool %}
privileged: true
{% endif %}
{% if docker_app_env | length > 0 %}
env_file: .env
{% endif %}
{% if docker_app_routing_mode == "host" and docker_app_ports | length > 0 %}
ports:
{% for port in docker_app_ports %}
- "{{ port.bind | default('0.0.0.0') }}:{{ port.host | default(port.container) }}:{{ port.container }}{% if port.protocol | default('tcp') != 'tcp' %}/{{ port.protocol }}{% endif %}"
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_service_port %}
# internal service port: {{ docker_app_service_port }}; not published on the Docker host
labels:
- traefik.enable=true
- traefik.http.routers.{{ docker_app_name }}.rule={{ docker_app_traefik_rule }}
- traefik.http.routers.{{ docker_app_name }}.entrypoints={{ docker_app_traefik_entrypoint }}
- traefik.http.routers.{{ docker_app_name }}.tls={{ docker_app_traefik_tls | bool | ternary('true', 'false') }}
{% if docker_app_traefik_middlewares | length > 0 %}
- traefik.http.routers.{{ docker_app_name }}.middlewares={{ docker_app_traefik_middlewares | join(',') }}
{% endif %}
- traefik.http.services.{{ docker_app_name }}.loadbalancer.server.port={{ docker_app_service_port }}
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% elif docker_app_labels | length > 0 %}
labels:
{% for key, value in docker_app_labels.items() %}
- {{ key }}={{ value }}
{% endfor %}
{% endif %}
{% if docker_app_volumes | length > 0 %}
volumes:
{% for volume in docker_app_volumes %}
- {{ volume }}
{% endfor %}
{% endif %}
{% if docker_app_devices | length > 0 %}
devices:
{% for device in docker_app_devices %}
- {{ device }}
{% endfor %}
{% endif %}
{% if docker_app_cap_add | length > 0 %}
cap_add:
{% for capability in docker_app_cap_add %}
- {{ capability }}
{% endfor %}
{% endif %}
{% if docker_app_group_add | length > 0 %}
group_add:
{% for group in docker_app_group_add %}
- "{{ group }}"
{% endfor %}
{% endif %}
{% if docker_app_extra_hosts | length > 0 %}
extra_hosts:
{% for host in docker_app_extra_hosts %}
- {{ host }}
{% endfor %}
{% endif %}
{% if docker_app_depends_on | length > 0 %}
depends_on:
{% for dependency in docker_app_depends_on %}
- {{ dependency }}
{% endfor %}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
- {{ docker_app_traefik_network }}
{% endif %}
{% if docker_app_routing_mode == "traefik" and docker_app_network_mode | length == 0 %}
networks:
{{ docker_app_traefik_network }}:
external: true
{% endif %}
+4
View File
@@ -0,0 +1,4 @@
{% for key, value in docker_app_env.items() %}
{{ key }}={{ value }}
{% endfor %}
+8
View File
@@ -0,0 +1,8 @@
# docker
Installs Docker Engine from Docker's official Debian repository, configures
`/etc/docker/daemon.json`, enables the service, and adds configured users to the
`docker` group.
Tags: `docker`, `packages`, `config`, `users`, `services`.
+18
View File
@@ -0,0 +1,18 @@
---
docker_apt_key_url: https://download.docker.com/linux/debian/gpg
docker_apt_key_path: /etc/apt/keyrings/docker.asc
docker_apt_architecture: "{{ ansible_architecture | regex_replace('x86_64', 'amd64') | regex_replace('aarch64', 'arm64') }}"
docker_apt_uri: https://download.docker.com/linux/debian
docker_packages:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-buildx-plugin
- docker-compose-plugin
docker_daemon_config:
log-driver: local
live-restore: true
storage-driver: overlay2
iptables: true
docker_service_state: started
docker_service_enabled: true
+1
View File
@@ -0,0 +1 @@
+6
View File
@@ -0,0 +1,6 @@
---
- name: restart docker
ansible.builtin.service:
name: docker
state: restarted
+69
View File
@@ -0,0 +1,69 @@
---
- name: Ensure apt keyring directory exists
ansible.builtin.file:
path: /etc/apt/keyrings
state: directory
owner: root
group: root
mode: "0755"
tags: [docker, packages]
- name: Install Docker repository signing key
ansible.builtin.get_url:
url: "{{ docker_apt_key_url }}"
dest: "{{ docker_apt_key_path }}"
owner: root
group: root
mode: "0644"
tags: [docker, packages]
- name: Configure Docker apt repository
ansible.builtin.deb822_repository:
name: docker
types:
- deb
uris:
- "{{ docker_apt_uri }}"
suites:
- "{{ ansible_distribution_release }}"
components:
- stable
architectures:
- "{{ docker_apt_architecture }}"
signed_by: "{{ docker_apt_key_path }}"
state: present
enabled: true
install_python_debian: true
tags: [docker, packages]
- name: Install Docker Engine and plugins
ansible.builtin.apt:
name: "{{ docker_packages }}"
state: present
update_cache: true
tags: [docker, packages]
- name: Configure Docker daemon
ansible.builtin.copy:
dest: /etc/docker/daemon.json
owner: root
group: root
mode: "0644"
content: "{{ docker_daemon_config | to_nice_json }}\n"
notify: restart docker
tags: [docker, config]
- name: Add users to docker group
ansible.builtin.user:
name: "{{ item }}"
groups: docker
append: true
loop: "{{ docker_users }}"
tags: [docker, users]
- name: Enable Docker service
ansible.builtin.service:
name: docker
enabled: "{{ docker_service_enabled }}"
state: "{{ docker_service_state }}"
tags: [docker, services]
+1
View File
@@ -0,0 +1 @@
+10
View File
@@ -0,0 +1,10 @@
# docker_app
Shared implementation role for single-container Docker Compose applications.
Application roles set `docker_app_*` variables and include this role.
It supports direct host-port publishing with UFW rules or Traefik routing via
labels and a shared Docker network.
Tags: `docker_app`, `docker`, `config`, `compose`, `firewall`.
+32
View File
@@ -0,0 +1,32 @@
---
docker_app_name: app
docker_app_dir: "/srv/docker/{{ docker_app_name }}"
docker_app_image: ""
docker_app_restart: unless-stopped
docker_app_command: ""
docker_app_user: ""
docker_app_network_mode: ""
docker_app_privileged: false
docker_app_pull: missing
docker_app_env: {}
docker_app_volumes: []
docker_app_ports: []
docker_app_devices: []
docker_app_cap_add: []
docker_app_group_add: []
docker_app_extra_hosts: []
docker_app_depends_on: []
docker_app_directories:
- "{{ docker_app_dir }}"
docker_app_routing_mode: "{{ nbops_service_routing_mode }}"
docker_app_domain: "{{ docker_app_name }}.{{ nbops_service_domain_suffix }}"
docker_app_service_port: null
docker_app_traefik_network: "{{ nbops_proxy_network }}"
docker_app_traefik_entrypoint: "{{ nbops_traefik_entrypoint }}"
docker_app_traefik_tls: "{{ nbops_traefik_tls }}"
docker_app_traefik_middlewares: []
docker_app_traefik_rule: "Host(`{{ docker_app_domain }}`)"
docker_app_labels: {}
docker_app_ufw_ports: "{{ docker_app_ports }}"
docker_app_env_template_src: env.j2
docker_app_compose_template_src: compose.yaml.j2
+1
View File
@@ -0,0 +1 @@
Loaded 100 of 411 files, more files were not shown because too many files have changed in this diff. Show more