diff --git a/_build/portainer_template_build.py b/_build/portainer_template_build.py index fbfd3fd..6bf5779 100644 --- a/_build/portainer_template_build.py +++ b/_build/portainer_template_build.py @@ -1,28 +1,78 @@ #!/usr/bin/env python3 -import os +""" +Portainer templates generator for nickyeoman/docker-compose-cookbooks + +Generates templates.json (Portainer-compatible) based on each project's +docker-compose.yml and sample.env/env-sample. + +Behavior: + - Uses master branch for raw GitHub stackfile URLs. + - Skips directories starting with "_" and those ending with "_dev" or "_notes". + - Skips projects whose docker-compose.yml contains "${VOL_PATH". + - Extracts README "## Overview" (exact match) for the description; default fallback. + - Parses sample.env or env-sample, strips inline comments, returns defaults. + - Resolves image lines (handles ${VAR:-default} and ${VAR}) using sample.env defaults. + - Parses ports into strings like "8000:80" or "8080:8080/tcp" for Portainer. + - Parses volumes and converts them into named Docker volumes: + - container path used as container + - named volume created as "-" and inserted into "bind" + - Produces a Portainer-friendly JSON structure: + { + "version": "3", + "templates": [ + { + "type": "docker-compose", + "title": "", + "name": "", + "note": "...", + "categories": ["Auto"], + "platform": "linux", + "logo": "...", + "description": "...", + "image": "...", + "stackfile": "https://raw.githubusercontent.com/..../master//docker-compose.yml", + "env": [ {name,label,default}, ... ], + "ports": ["80:80", ...], + "volumes": [ {"container":"/path", "bind":""}, ... ] + } + ] + } +""" +from pathlib import Path +import argparse import json import re import requests -from pathlib import Path +import sys -# --------------------------------------------------------- -# Paths -# --------------------------------------------------------- +# Constants SCRIPT_DIR = Path(__file__).resolve().parent REPO_ROOT = SCRIPT_DIR.parent OUTPUT_FILE = REPO_ROOT / "templates.json" +GITHUB_RAW_BASE = "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master" +ASSETS_BASE = "https://i.4lt.ca/cookbook" +SKIP_PAT = "${VOL_PATH" -# --------------------------------------------------------- -# Extract "Overview" section from README.md (strict) -# --------------------------------------------------------- +# ------------------------- +# Helpers +# ------------------------- +def read_text_safe(p: Path) -> str: + try: + return p.read_text() + except Exception: + return "" + +# ------------------------- +# Extract "Overview" section from README.md (exact "## Overview") +# ------------------------- def extract_overview(readme_path: Path, project_name: str) -> str: - if not readme_path.exists(): + text = read_text_safe(readme_path) + if not text: return f"{project_name} description to come" - lines = readme_path.read_text().splitlines() + lines = text.splitlines() overview = [] in_section = False - for line in lines: stripped = line.strip() if stripped == "## Overview": @@ -33,69 +83,98 @@ def extract_overview(readme_path: Path, project_name: str) -> str: break if stripped: overview.append(stripped) - if not overview: return f"{project_name} description to come" - return " ".join(overview) -# --------------------------------------------------------- -# Logo URL mapping with existence check -# --------------------------------------------------------- +# ------------------------- +# Logo URL lookup (skip HEAD if nologo True) +# ------------------------- def find_logo_url(dir_path: Path, nologo: bool = False) -> str: - base_url = "https://i.4lt.ca/cookbooks/" name = dir_path.name - logo_url = f"{base_url}{name}.png" - + logo_url = f"{ASSETS_BASE}/{name}.png" + default = f"{ASSETS_BASE}/default.png" if nologo: - return f"{base_url}default.png" - + return default try: - resp = requests.head(logo_url, timeout=5) + resp = requests.head(logo_url, timeout=4) if resp.status_code == 200: return logo_url - except requests.RequestException: + except Exception: pass + return default - return f"{base_url}default.png" +# ------------------------- +# Parse env defaults from sample.env or env-sample (strip comments) +# Returns list of dicts { "name": ..., "default": ... } +# ------------------------- +def parse_env_vars(dir_path: Path): + candidates = [dir_path / "sample.env", dir_path / "env-sample"] + env_file = None + for p in candidates: + if p.exists(): + env_file = p + break + if not env_file: + return [] + + env_list = [] + for raw in read_text_safe(env_file).splitlines(): + line = raw.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + key, val = line.split("=", 1) + key = key.strip() + # strip inline comments after # + val = val.split("#", 1)[0].strip() + # remove surrounding quotes + val = val.strip().strip('"').strip("'") + env_list.append({"name": key, "default": val}) + return env_list + +# ------------------------- +# Resolve the first "image:" line from docker-compose.yml +# Supports ${VAR:-default} and ${VAR} +# ------------------------- +VAR_RE = re.compile(r"\$\{([^:}]+)(?:[:-]([^}]+))?\}") -# --------------------------------------------------------- -# Parse image from docker-compose.yml using sample.env defaults -# --------------------------------------------------------- def parse_image(compose_path: Path, env_vars: list) -> str: - if not compose_path.exists(): + text = read_text_safe(compose_path) + if not text: return "" env_defaults = {v["name"]: v["default"] for v in env_vars} - for line in compose_path.read_text().splitlines(): - line = line.strip() - if line.startswith("image:"): - image = line.split("image:", 1)[1].strip().strip('"').strip("'") - - # Handle ${VAR:-default} or ${VAR} - def replace_var(match): - var_name = match.group(1) - bash_default = match.group(2) - return env_defaults.get(var_name, bash_default if bash_default else "") - - image = re.sub(r"\$\{([^:}]+)(?:[:-]([^}]+))?\}", replace_var, image) + for line in text.splitlines(): + stripped = line.strip() + if stripped.startswith("image:"): + image = stripped.split("image:", 1)[1].strip().strip('"').strip("'") + # replace ${VAR:-default} or ${VAR} using env_defaults + def repl(m): + var = m.group(1) + default = m.group(2) + return env_defaults.get(var, default or "") + image = VAR_RE.sub(repl, image) return image - return "" -# --------------------------------------------------------- -# Parse ports from docker-compose.yml -# Returns a list of dicts: {"container": port, "published": port, "protocol": "tcp"} -# --------------------------------------------------------- +# ------------------------- +# Parse ports into a list of strings Portainer likes +# Examples supported: +# - "8080:80" +# - "9000" +# - "9000:9000/tcp" +# - "53:53/udp" +# ------------------------- def parse_ports(compose_path: Path): - if not compose_path.exists(): + text = read_text_safe(compose_path) + if not text: return [] - ports_list = [] - lines = compose_path.read_text().splitlines() + ports = [] + lines = text.splitlines() in_ports = False - for line in lines: stripped = line.strip() if stripped.startswith("ports:"): @@ -104,54 +183,34 @@ def parse_ports(compose_path: Path): if in_ports: if not stripped or not stripped.startswith("-"): break - + # get port string, strip comments/quotes port_str = stripped[1:].strip().split("#", 1)[0].strip().strip('"').strip("'") if not port_str: continue + # normalize space + port_str = port_str.replace(" ", "") + # handle protocol suffix; keep as-is for Portainer + ports.append(port_str) + return ports - protocol = "tcp" - if "/" in port_str: - port_part, protocol_part = port_str.split("/", 1) - protocol = protocol_part.strip() - else: - port_part = port_str - - if ":" in port_part: - host_port, container_port = port_part.split(":", 1) - else: - container_port = port_part - host_port = container_port - - try: - container_port = int(container_port.strip()) - host_port = int(host_port.strip()) - except ValueError: - continue - - ports_list.append({ - "container": container_port, - "published": host_port, - "protocol": protocol - }) - - return ports_list - -# --------------------------------------------------------- -# Parse volumes from docker-compose.yml -# Returns a list of dicts: {"name": "unique-name", "container": "/container/path"} -# --------------------------------------------------------- +# ------------------------- +# Parse volumes: +# - Accept mapping forms: +# host:container[:ro|rw] +# container[:ro|rw] +# named-volume:container[:ro|rw] +# - For Portainer templates we'll provide: +# {"container": "/path/in/container", "bind": ""} +# where is generated as "-" +# ------------------------- def parse_volumes(compose_path: Path, stack_name: str): - """ - Parse volumes from docker-compose.yml and generate Portainer-ready entries. - Host paths and VOL_PATH references are ignored; only container paths are used. - """ - if not compose_path.exists(): + text = read_text_safe(compose_path) + if not text: return [] - volumes = [] - lines = compose_path.read_text().splitlines() + vols = [] + lines = text.splitlines() in_volumes = False - for line in lines: stripped = line.strip() if stripped.startswith("volumes:"): @@ -160,102 +219,94 @@ def parse_volumes(compose_path: Path, stack_name: str): if in_volumes: if not stripped or not stripped.startswith("-"): break - - # Strip "- ", quotes, inline comments - volume_str = stripped[1:].strip().split("#", 1)[0].strip().strip('"').strip("'") - if not volume_str: + vol_line = stripped[1:].strip().split("#", 1)[0].strip().strip('"').strip("'") + if not vol_line: continue - - # Split host:container[:ro|rw] - parts = volume_str.split(":") + # split from right to handle host:container:mode (we want container and ignore host) + parts = vol_line.rsplit(":", 2) # at most 3 parts + # container part is last if there are 2 or more parts, else first if len(parts) == 1: container_path = parts[0].strip() else: - container_path = parts[-1].strip() # always take the last part as container path - - # Remove :ro or :rw suffix if present + container_path = parts[-2].strip() if len(parts) == 3 else parts[-1].strip() + # explanation: + # - "host:container" -> parts len 2 -> parts[-1] == container + # - "host:container:ro" -> parts len 3 -> parts[-2] == container + # final strip of mode suffix if any left container_path = re.sub(r":(ro|rw)$", "", container_path) + # ensure container path starts with / + if not container_path.startswith("/"): + # if it's like "config.yaml" or similar, prefix with / + container_path = "/" + container_path.lstrip("/") - # Generate unique name for Portainer from stack + container path - name = f"{stack_name}-{container_path.strip('/').replace('/', '-')}" - if not name: - name = f"{stack_name}-volume" + # generate a safe named volume + safe = container_path.strip("/").replace("/", "-").replace(".", "-") + if not safe: + safe = "data" + vol_name = f"{stack_name}-{safe}" + vols.append({"container": container_path, "bind": vol_name}) + return vols - volumes.append({"name": name, "container": container_path}) - - return volumes - -# --------------------------------------------------------- -# Parse environment variables from sample.env or env-sample -# Returns a list of dicts: {"name": ..., "default": ...} -# --------------------------------------------------------- -def parse_env_vars(dir_path: Path): - env_file = None - if (dir_path / "sample.env").exists(): - env_file = dir_path / "sample.env" - elif (dir_path / "env-sample").exists(): - env_file = dir_path / "env-sample" - - if not env_file: - return [] - - env_list = [] - for line in env_file.read_text().splitlines(): - line = line.strip() - if not line or line.startswith("#"): - continue - if "=" in line: - key, value = line.split("=", 1) - key = key.strip() - value = value.split("#", 1)[0].strip().strip('"').strip("'") - if key: - env_list.append({"name": key, "default": value}) - - return env_list - -# --------------------------------------------------------- -# Build template object for a stack directory -# --------------------------------------------------------- +# ------------------------- +# Build a Portainer template object for a project directory +# ------------------------- def generate_template_object(dir_path: Path, nologo: bool = False): name = dir_path.name compose_file = dir_path / "docker-compose.yml" readme_file = dir_path / "README.md" description = extract_overview(readme_file, name) - logo = find_logo_url(dir_path, nologo=False) + logo = find_logo_url(dir_path, nologo=nologo) env_vars = parse_env_vars(dir_path) image = parse_image(compose_file, env_vars) ports = parse_ports(compose_file) volumes = parse_volumes(compose_file, name) - env_entries = [ - {"name": v["name"], "label": v["name"], "default": v["default"]} - for v in env_vars if v["default"] is not None - ] + # env entries: only include those with a name + env_entries = [] + for v in env_vars: + if not v.get("name"): + continue + # leave default as empty string if missing (Portainer accepts empty) + env_entries.append({"name": v["name"], "label": v["name"], "default": v.get("default", "")}) - return { - "type": 1, + # Build stackfile raw URL on master branch + stackfile_url = f"{GITHUB_RAW_BASE}/{name}/docker-compose.yml" + + tpl = { + "type": "docker-compose", "title": name, - "description": description, + "name": name, "note": "Auto-generated template from repository", "categories": ["Auto"], "platform": "linux", "logo": logo, + "description": description, + # optional convenience field: image "image": image, - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": f"{name}/docker-compose.yml", - "stackfile_plain": False - }, + # Portainer expects a full stackfile URL + "stackfile": stackfile_url, "env": env_entries, - "ports": ports, - "volumes": volumes } -# --------------------------------------------------------- -# Main script logic -# --------------------------------------------------------- + # ports: Portainer accepts list of strings like "8000:80" or "53:53/udp" + if ports: + tpl["ports"] = ports + + # volumes: produce list of objects with container and bind (named volume) + if volumes: + tpl["volumes"] = volumes + + return tpl + +# ------------------------- +# Main +# ------------------------- def main(): + parser = argparse.ArgumentParser(description="Generate Portainer templates.json from compose cookbooks") + parser.add_argument("--nologo", action="store_true", help="skip HEAD requests for logos (faster, dev)") + args = parser.parse_args() + templates = [] for item in REPO_ROOT.iterdir(): @@ -270,27 +321,20 @@ def main(): if not compose_path.exists(): continue - # --------------------------------------------- - # Skip stacks still using ${VOL_PATH - # --------------------------------------------- - compose_text = compose_path.read_text() - if "${VOL_PATH" in compose_text: + # skip projects still using VOL_PATH (developer needs to fix) + if SKIP_PAT in read_text_safe(compose_path): + print(f"Skipping {item.name}: still uses ${{VOL_PATH}}") continue - # --------------------------------------------- - # Generate Portainer template object - # --------------------------------------------- - templates.append(generate_template_object(item, nologo=True)) - - # Write output file - output_data = { - "version": "3", - "templates": templates - } - - OUTPUT_FILE.write_text(json.dumps(output_data, indent=2)) - print(f"Generated {OUTPUT_FILE} with templates for Docker Compose stacks.") + tpl = generate_template_object(item, nologo=args.nologo) + templates.append(tpl) + output = {"version": "3", "templates": templates} + # Write atomically if possible + tmp = OUTPUT_FILE.with_suffix(".tmp") + tmp.write_text(json.dumps(output, indent=2)) + tmp.replace(OUTPUT_FILE) + print(f"Generated {OUTPUT_FILE} with {len(templates)} templates.") if __name__ == "__main__": main() diff --git a/templates.json b/templates.json index e415c7f..6b90ad5 100644 --- a/templates.json +++ b/templates.json @@ -2,21 +2,18 @@ "version": "3", "templates": [ { - "type": 1, + "type": "docker-compose", "title": "wallabag", - "description": "Docker Hub: https://hub.docker.com/r/wallabag/wallabag Project: Docker Compose Example: https://github.com/wallabag/docker Proxy Port: 80", + "name": "wallabag", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Docker Hub: https://hub.docker.com/r/wallabag/wallabag Project: Docker Compose Example: https://github.com/wallabag/docker Proxy Port: 80", "image": "wallabag/wallabag:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "wallabag/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/wallabag/docker-compose.yml", "env": [ { "name": "WALLABAG_TZ", @@ -89,30 +86,26 @@ "default": "Your wallabag instance" } ], - "ports": [], "volumes": [ { - "name": "wallabag-var-www-wallabag-web-assets-images", - "container": "/var/www/wallabag/web/assets/images" + "container": "/var/www/wallabag/web/assets/images", + "bind": "wallabag-var-www-wallabag-web-assets-images" } ] }, { - "type": 1, + "type": "docker-compose", "title": "thunderbird", - "description": "Git Hub: https://github.com/jlesage/docker-thunderbird Proxy Port: 5800", + "name": "thunderbird", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/thunderbird.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Git Hub: https://github.com/jlesage/docker-thunderbird Proxy Port: 5800", "image": "jlesage/thunderbird:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "thunderbird/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/thunderbird/docker-compose.yml", "env": [ { "name": "THUNDERBIRD_IMAGE", @@ -141,39 +134,32 @@ } ], "ports": [ - { - "container": 5800, - "published": 5800, - "protocol": "tcp" - } + "5800:5800" ], "volumes": [ { - "name": "thunderbird-config", - "container": "/config" + "container": "/config", + "bind": "thunderbird-config" }, { - "name": "thunderbird-export", - "container": "/export" + "container": "/export", + "bind": "thunderbird-export" } ] }, { - "type": 1, + "type": "docker-compose", "title": "mariadb", - "description": "mariadb description to come", + "name": "mariadb", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "mariadb description to come", "image": "mariadb:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "mariadb/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/mariadb/docker-compose.yml", "env": [ { "name": "MARIADB_IMAGE", @@ -201,61 +187,50 @@ "default": "dbuser" } ], - "ports": [], "volumes": [ { - "name": "mariadb-var-lib-mysql", - "container": "/var/lib/mysql" + "container": "/var/lib/mysql", + "bind": "mariadb-var-lib-mysql" } ] }, { - "type": 1, + "type": "docker-compose", "title": "dozzle", - "description": "dozzle description to come", + "name": "dozzle", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "dozzle description to come", "image": "amir20/dozzle:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "dozzle/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/dozzle/docker-compose.yml", "env": [], "ports": [ - { - "container": 8080, - "published": 8080, - "protocol": "tcp" - } + "8080:8080" ], "volumes": [ { - "name": "dozzle-var-run-docker.sock", - "container": "/var/run/docker.sock" + "container": "/var/run/docker.sock", + "bind": "dozzle-var-run-docker-sock" } ] }, { - "type": 1, + "type": "docker-compose", "title": "passbolt", - "description": "passbolt description to come", + "name": "passbolt", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "passbolt description to come", "image": "mariadb:10.4.11", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "passbolt/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/passbolt/docker-compose.yml", "env": [ { "name": "PREFIX", @@ -292,92 +267,73 @@ "label": "NETWORK_NAME", "default": "traefik_web" } - ], - "ports": [], - "volumes": [] + ] }, { - "type": 1, + "type": "docker-compose", "title": "unami", - "description": "unami description to come", + "name": "unami", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "unami description to come", "image": "ghcr.io/umami-software/umami:postgresql-latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "unami/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/unami/docker-compose.yml", "env": [], "ports": [ - { - "container": 3000, - "published": 3000, - "protocol": "tcp" - } + "3000:3000" ], "volumes": [ { - "name": "unami-var-lib-postgresql-data", - "container": "/var/lib/postgresql/data" + "container": "/var/lib/postgresql/data", + "bind": "unami-var-lib-postgresql-data" } ] }, { - "type": 1, + "type": "docker-compose", "title": "peertube", - "description": "peertube description to come", + "name": "peertube", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "peertube description to come", "image": "chocobozzz/peertube:production-bookworm", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "peertube/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/peertube/docker-compose.yml", "env": [], "ports": [ - { - "container": 9000, - "published": 9000, - "protocol": "tcp" - } + "9000:9000" ], "volumes": [ { - "name": "peertube-data", - "container": "/data" + "container": "/data", + "bind": "peertube-data" }, { - "name": "peertube-config", - "container": "/config" + "container": "/config", + "bind": "peertube-config" } ] }, { - "type": 1, + "type": "docker-compose", "title": "phpmyadmin", - "description": "Docker Hub: https://hub.docker.com/r/phpmyadmin/phpmyadmin/tags Proxy Port: 80 Not for production, there is no password protection by default.", + "name": "phpmyadmin", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Docker Hub: https://hub.docker.com/r/phpmyadmin/phpmyadmin/tags Proxy Port: 80 Not for production, there is no password protection by default.", "image": "phpmyadmin/phpmyadmin:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "phpmyadmin/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/phpmyadmin/docker-compose.yml", "env": [ { "name": "COOKBOOK", @@ -435,60 +391,48 @@ "default": "200M" } ], - "ports": [], "volumes": [ { - "name": "phpmyadmin-ro", - "container": "ro" + "container": "/etc/timezone", + "bind": "phpmyadmin-etc-timezone" }, { - "name": "phpmyadmin-ro", - "container": "ro" + "container": "/etc/localtime", + "bind": "phpmyadmin-etc-localtime" } ] }, { - "type": 1, + "type": "docker-compose", "title": "penpot", - "description": "penpot description to come", + "name": "penpot", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "penpot description to come", "image": "penpotapp/frontend:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "penpot/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/penpot/docker-compose.yml", "env": [], "ports": [ - { - "container": 8080, - "published": 9001, - "protocol": "tcp" - } - ], - "volumes": [] + "9001:8080" + ] }, { - "type": 1, + "type": "docker-compose", "title": "pihole", - "description": "pihole description to come", + "name": "pihole", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "pihole description to come", "image": "pihole/pihole:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "pihole/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/pihole/docker-compose.yml", "env": [ { "name": "VOL_PATH", @@ -507,49 +451,34 @@ } ], "ports": [ - { - "container": 53, - "published": 53, - "protocol": "tcp" - }, - { - "container": 53, - "published": 53, - "protocol": "udp" - }, - { - "container": 80, - "published": 80, - "protocol": "tcp" - } + "53:53/tcp", + "53:53/udp", + "80:80/tcp" ], "volumes": [ { - "name": "pihole-etc-pihole", - "container": "/etc/pihole" + "container": "/etc/pihole", + "bind": "pihole-etc-pihole" }, { - "name": "pihole-etc-dnsmasq.d", - "container": "/etc/dnsmasq.d" + "container": "/etc/dnsmasq.d", + "bind": "pihole-etc-dnsmasq-d" } ] }, { - "type": 1, + "type": "docker-compose", "title": "matomo", - "description": "matomo description to come", + "name": "matomo", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "matomo description to come", "image": "mariadb:10.4.0", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "matomo/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/matomo/docker-compose.yml", "env": [ { "name": "PREFIX", @@ -582,121 +511,95 @@ "default": "3.13.5-apache" } ], - "ports": [], "volumes": [ { - "name": "matomo-var-lib-mysql", - "container": "/var/lib/mysql" + "container": "/var/lib/mysql", + "bind": "matomo-var-lib-mysql" } ] }, { - "type": 1, + "type": "docker-compose", "title": "joomla", + "name": "joomla", + "note": "Auto-generated template from repository", + "categories": [ + "Auto" + ], + "platform": "linux", + "logo": "https://i.4lt.ca/cookbook/default.png", "description": "joomla description to come", - "note": "Auto-generated template from repository", - "categories": [ - "Auto" - ], - "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", "image": "mariadb:10.7.1 # https://hub.docker.com/_/mariadb", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "joomla/docker-compose.yml", - "stackfile_plain": false - }, - "env": [], - "ports": [], - "volumes": [] + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/joomla/docker-compose.yml", + "env": [] }, { - "type": 1, + "type": "docker-compose", "title": "etherpad", + "name": "etherpad", + "note": "Auto-generated template from repository", + "categories": [ + "Auto" + ], + "platform": "linux", + "logo": "https://i.4lt.ca/cookbook/default.png", "description": "etherpad description to come", - "note": "Auto-generated template from repository", - "categories": [ - "Auto" - ], - "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", "image": "", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "etherpad/docker-compose.yml", - "stackfile_plain": false - }, - "env": [], - "ports": [], - "volumes": [] + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/etherpad/docker-compose.yml", + "env": [] }, { - "type": 1, + "type": "docker-compose", "title": "posthog", - "description": "posthog description to come", + "name": "posthog", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "posthog description to come", "image": "", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "posthog/docker-compose.yml", - "stackfile_plain": false - }, - "env": [], - "ports": [], - "volumes": [] + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/posthog/docker-compose.yml", + "env": [] }, { - "type": 1, + "type": "docker-compose", "title": "memos", - "description": "memos description to come", + "name": "memos", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "memos description to come", "image": "neosmemo/memos:stable", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "memos/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/memos/docker-compose.yml", "env": [], "ports": [ - { - "container": 5230, - "published": 5230, - "protocol": "tcp" - } + "5230:5230" ], "volumes": [ { - "name": "memos-var-opt-memos", - "container": "/var/opt/memos" + "container": "/var/opt/memos", + "bind": "memos-var-opt-memos" } ] }, { - "type": 1, + "type": "docker-compose", "title": "redis", - "description": "Docker Hub: https://hub.docker.com/_/redis", + "name": "redis", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Docker Hub: https://hub.docker.com/_/redis", "image": "redis:alpine", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "redis/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/redis/docker-compose.yml", "env": [ { "name": "COOKBOOK", @@ -708,26 +611,21 @@ "label": "REDIS_IMAGE", "default": "redis:alpine" } - ], - "ports": [], - "volumes": [] + ] }, { - "type": 1, + "type": "docker-compose", "title": "listmonk", - "description": "listmonk description to come", + "name": "listmonk", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "listmonk description to come", "image": "postgres:13-alpine", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "listmonk/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/listmonk/docker-compose.yml", "env": [ { "name": "POSTGRES_USER", @@ -751,99 +649,85 @@ } ], "ports": [ - { - "container": 9000, - "published": 9000, - "protocol": "tcp" - } + "9000:9000" ], "volumes": [ { - "name": "listmonk-var-lib-postgresql-data", - "container": "/var/lib/postgresql/data" + "container": "/var/lib/postgresql/data", + "bind": "listmonk-var-lib-postgresql-data" } ] }, { - "type": 1, + "type": "docker-compose", "title": "shlink", - "description": "shlink description to come", + "name": "shlink", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "shlink description to come", "image": "mariadb:10.7.1 # https://hub.docker.com/_/mariadb", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "shlink/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/shlink/docker-compose.yml", "env": [], "ports": [ - { - "container": 80, - "published": 8003, - "protocol": "tcp" - } + "8003:80" ], "volumes": [ { - "name": "shlink-var-lib-mysql", - "container": "/var/lib/mysql" + "container": "/var/lib/mysql", + "bind": "shlink-var-lib-mysql" }, { - "name": "shlink-ro", - "container": "ro" + "container": "/etc/timezone", + "bind": "shlink-etc-timezone" }, { - "name": "shlink-ro", - "container": "ro" + "container": "/etc/localtime", + "bind": "shlink-etc-localtime" } ] }, { - "type": 1, + "type": "docker-compose", "title": "authentik", - "description": "authentik description to come", + "name": "authentik", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "authentik description to come", "image": "docker.io/library/postgres:16-alpine", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "authentik/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/authentik/docker-compose.yml", "env": [], - "ports": [], + "ports": [ + "${COMPOSE_PORT_HTTP:-9000}:9000", + "${COMPOSE_PORT_HTTPS:-9443}:9443" + ], "volumes": [ { - "name": "authentik-var-lib-postgresql-data", - "container": "/var/lib/postgresql/data" + "container": "/var/lib/postgresql/data", + "bind": "authentik-var-lib-postgresql-data" } ] }, { - "type": 1, + "type": "docker-compose", "title": "homepage", - "description": "Project: https://gethomepage.dev/latest/ Docker Compose Example: https://gethomepage.dev/latest/installation/docker/ Proxy Port: 3000 If you want to use the optional docker socket, you must run as root.", + "name": "homepage", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Project: https://gethomepage.dev/latest/ Docker Compose Example: https://gethomepage.dev/latest/installation/docker/ Proxy Port: 3000 If you want to use the optional docker socket, you must run as root.", "image": "ghcr.io/gethomepage/homepage:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "homepage/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/homepage/docker-compose.yml", "env": [ { "name": "HOMEPAGE_IMAGE", @@ -856,65 +740,54 @@ "default": "./config" } ], - "ports": [], "volumes": [ { - "name": "homepage-app-config", - "container": "/app/config" + "container": "/app/config", + "bind": "homepage-app-config" }, { - "name": "homepage-var-run-docker.sock", - "container": "/var/run/docker.sock" + "container": "/var/run/docker.sock", + "bind": "homepage-var-run-docker-sock" } ] }, { - "type": 1, + "type": "docker-compose", "title": "docmost", - "description": "docmost description to come", + "name": "docmost", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "docmost description to come", "image": "docmost/docmost:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "docmost/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/docmost/docker-compose.yml", "env": [], "ports": [ - { - "container": 3000, - "published": 8000, - "protocol": "tcp" - } + "8000:3000" ], "volumes": [ { - "name": "docmost-app-data-storage", - "container": "/app/data/storage" + "container": "/app/data/storage", + "bind": "docmost-app-data-storage" } ] }, { - "type": 1, + "type": "docker-compose", "title": "bookstack", - "description": "BookStack is a self-hosted wiki platform for organizing and storing documentation in a simple, book/chapter/page structure. This Docker Compose stack allows easy deployment with persistent storage.", + "name": "bookstack", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/bookstack.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "BookStack is a self-hosted wiki platform for organizing and storing documentation in a simple, book/chapter/page structure. This Docker Compose stack allows easy deployment with persistent storage.", "image": "solidnerd/bookstack:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "bookstack/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/bookstack/docker-compose.yml", "env": [ { "name": "BOOKSTACK_APP_KEY", @@ -988,43 +861,36 @@ } ], "ports": [ - { - "container": 8080, - "published": 8080, - "protocol": "tcp" - } + "8080:8080" ], "volumes": [ { - "name": "bookstack-usr-local-etc-php-php.ini", - "container": "/usr/local/etc/php/php.ini" + "container": "/usr/local/etc/php/php.ini", + "bind": "bookstack-usr-local-etc-php-php-ini" }, { - "name": "bookstack-var-www-bookstack-storage-uploads", - "container": "/var/www/bookstack/storage/uploads" + "container": "/var/www/bookstack/storage/uploads", + "bind": "bookstack-var-www-bookstack-storage-uploads" }, { - "name": "bookstack-var-www-bookstack-public-uploads", - "container": "/var/www/bookstack/public/uploads" + "container": "/var/www/bookstack/public/uploads", + "bind": "bookstack-var-www-bookstack-public-uploads" } ] }, { - "type": 1, + "type": "docker-compose", "title": "rocketchat", - "description": "rocketchat description to come", + "name": "rocketchat", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "rocketchat description to come", "image": "rocketchat/rocket.chat:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "rocketchat/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/rocketchat/docker-compose.yml", "env": [ { "name": "PREFIX", @@ -1041,65 +907,53 @@ "label": "ROCKETCHAT_V", "default": "3.1.1" } - ], - "ports": [], - "volumes": [] + ] }, { - "type": 1, + "type": "docker-compose", "title": "dockge", - "description": "dockge description to come", + "name": "dockge", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "dockge description to come", "image": "louislam/dockge:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "dockge/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/dockge/docker-compose.yml", "env": [], "ports": [ - { - "container": 5001, - "published": 5001, - "protocol": "tcp" - } + "5001:5001" ], "volumes": [ { - "name": "dockge-var-run-docker.sock", - "container": "/var/run/docker.sock" + "container": "/var/run/docker.sock", + "bind": "dockge-var-run-docker-sock" }, { - "name": "dockge-app-data", - "container": "/app/data" + "container": "/app/data", + "bind": "dockge-app-data" }, { - "name": "dockge-opt-stacks", - "container": "/opt/stacks" + "container": "/opt/stacks", + "bind": "dockge-opt-stacks" } ] }, { - "type": 1, + "type": "docker-compose", "title": "invoiceninja", - "description": "invoiceninja description to come", + "name": "invoiceninja", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "invoiceninja description to come", "image": "invoiceninja/invoiceninja-debian:-latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "invoiceninja/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/invoiceninja/docker-compose.yml", "env": [ { "name": "APP_URL", @@ -1303,30 +1157,22 @@ } ], "ports": [ - { - "container": 80, - "published": 8000, - "protocol": "tcp" - } - ], - "volumes": [] + "8000:80" + ] }, { - "type": 1, + "type": "docker-compose", "title": "prowlarr", - "description": "prowlarr description to come", + "name": "prowlarr", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "prowlarr description to come", "image": "ghcr.io/hotio/prowlarr:release", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "prowlarr/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/prowlarr/docker-compose.yml", "env": [ { "name": "VOL_PATH", @@ -1345,35 +1191,28 @@ } ], "ports": [ - { - "container": 9696, - "published": 9696, - "protocol": "tcp" - } + "9696:9696" ], "volumes": [ { - "name": "prowlarr-config", - "container": "/config" + "container": "/-./data/prowlarr/config}", + "bind": "prowlarr----data-prowlarr-config}" } ] }, { - "type": 1, + "type": "docker-compose", "title": "archivebox", - "description": "Docker Hub: https://hub.docker.com/r/archivebox/archivebox/tags Docker Compose Example: https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/docker-compose.yml Project: https://archivebox.io/ Proxy Port: 8000", + "name": "archivebox", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "Docker Hub: https://hub.docker.com/r/archivebox/archivebox/tags Docker Compose Example: https://raw.githubusercontent.com/ArchiveBox/ArchiveBox/dev/docker-compose.yml Project: https://archivebox.io/ Proxy Port: 8000", "image": "archivebox/archivebox:sha-1d49bee", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "archivebox/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/archivebox/docker-compose.yml", "env": [ { "name": "ARCHIVEBOX_IMAGE", @@ -1381,42 +1220,34 @@ "default": "archivebox/archivebox:sha-1d49bee" } ], - "ports": [], "volumes": [ { - "name": "archivebox-data", - "container": "/data" + "container": "/data", + "bind": "archivebox-data" } ] }, { - "type": 1, + "type": "docker-compose", "title": "hedgedoc", - "description": "hedgedoc description to come", + "name": "hedgedoc", "note": "Auto-generated template from repository", "categories": [ "Auto" ], "platform": "linux", - "logo": "https://i.4lt.ca/cookbooks/default.png", + "logo": "https://i.4lt.ca/cookbook/default.png", + "description": "hedgedoc description to come", "image": "ghcr.io/hedgedoc/hedgedoc:latest", - "repository": { - "url": "https://github.com/nickyeoman/docker-compose-cookbooks", - "stackfile": "hedgedoc/docker-compose.yml", - "stackfile_plain": false - }, + "stackfile": "https://raw.githubusercontent.com/nickyeoman/docker-compose-cookbooks/master/hedgedoc/docker-compose.yml", "env": [], "ports": [ - { - "container": 3000, - "published": 3000, - "protocol": "tcp" - } + "3000:3000" ], "volumes": [ { - "name": "hedgedoc-hedgedoc-data", - "container": "/hedgedoc/data" + "container": "/hedgedoc/data", + "bind": "hedgedoc-hedgedoc-data" } ] }