feat(oci): add Authentik catalog profile

This commit is contained in:
Jonatan
2026-10-30 19:24:01 +01:00
parent 89531bbab8
commit 3b15e14625
5 changed files with 1469 additions and 3 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<!-- Generated from oci/catalog/verification.json by .github/scripts/oci_validation.py. Do not edit by hand. -->
44 of 530 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
44 of 531 applications in the OCI catalog have been tested for real: 41 in the ProxMenux lab and 3 by the community. The OCI installer shows them as verified, with a ✓ in the lists.
Each test describes the scenario that was run and names the image it ran on. It does not cover every possible configuration of the application, and a newer image has not been tested until someone reports it.
+645
View File
@@ -0,0 +1,645 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-authentik",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authentik"
},
"tagline": {
"en_US": "Identity provider for single sign-on and access control"
},
"description": {
"en_US": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authentik",
"developer": "Authentik",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9000,
"path": "/"
},
"website": "https://goauthentik.io/",
"documentation": "https://docs.goauthentik.io/install-config/install/docker-compose/",
"repository": "https://github.com/goauthentik/authentik",
"tips": [
"The server and worker share /data and /templates through host directories; those directories are not included in Proxmox CT backups.",
"The worker Docker-socket mount is omitted. Use the embedded outpost or deploy and manage a separate outpost manually."
],
"mini_changelog": [],
"display_version": "2026.8.5",
"updated_at": "2026-10-21"
},
"source": {
"provider": "goauthentik",
"repository": "https://github.com/goauthentik/authentik",
"default_branch": "main",
"revision": "4ec0d19b98da3c329781894cd550da95d3033954",
"readme_raw_url": "https://goauthentik.io/version/2026.8/lifecycle/container/compose.yml",
"image_repository_url": "https://github.com/goauthentik/authentik/pkgs/container/server",
"readme_pushed_at": "2026-10-21T12:57:59Z",
"compose_sha256": "a3e138b5a85844d15c4a7ba6339997e43c0479d9d198582c67dd19523aadc694",
"generated_at": "2026-10-30T18:09:01+00:00"
},
"container_contract": {
"service_name": "server",
"container_name": "authentik-server",
"image": {
"reference": "ghcr.io/goauthentik/server:2026.8.5",
"registry": "ghcr.io",
"repository": "ghcr.io/goauthentik/server",
"tag": "2026.8.5",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "AUTHENTIK_POSTGRESQL__HOST",
"example": "postgresql",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__NAME",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__PASSWORD",
"example": "${GENERATED_GENERATED_PG_PASS}",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__USER",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_SECRET_KEY",
"example": "${GENERATED_GENERATED_AUTHENTIK_SECRET_KEY}",
"required": true,
"sensitive": true,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/data",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/templates",
"compose_source_example": "./custom-templates",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 9000,
"published_example": 9000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 9443,
"published_example": 9443,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine"
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "name: authentik\nservices:\n postgresql:\n environment:\n POSTGRES_DB: authentik\n POSTGRES_PASSWORD: ${GENERATED_PG_PASS}\n POSTGRES_USER: authentik\n healthcheck:\n interval: 30s\n retries: 5\n start_period: 20s\n test:\n - CMD-SHELL\n - pg_isready -h 127.0.0.1 -d authentik -U authentik\n timeout: 5s\n image: docker.io/library/postgres:16-alpine\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql/data\n server:\n command: server\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n ports:\n - 9000:9000\n - 9443:9443\n restart: unless-stopped\n shm_size: 512mb\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./custom-templates\n target: /templates\n worker:\n command: worker\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n restart: unless-stopped\n shm_size: 512mb\n user: root\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./certs\n target: /certs\n - read_only: false\n source: ./custom-templates\n target: /templates\nvolumes:\n database: {}\n"
},
"compose_stack": {
"project_name": "authentik",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "server",
"service_count": 3,
"services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"POSTGRES_DB": "authentik",
"POSTGRES_PASSWORD": "${GENERATED_PG_PASS}",
"POSTGRES_USER": "authentik"
},
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": [
"CMD-SHELL",
"pg_isready -h 127.0.0.1 -d authentik -U authentik"
],
"timeout": "5s"
},
"image": "docker.io/library/postgres:16-alpine",
"restart": "unless-stopped",
"volumes": [
{
"source": "database",
"target": "/var/lib/postgresql/data",
"read_only": false
}
]
}
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": false,
"role": "dependency",
"vmid_offset": 2,
"depends_on": [
"postgresql"
],
"frontend_network": false,
"private_network": true,
"compose": {
"command": "worker",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"restart": "unless-stopped",
"shm_size": "512mb",
"user": "root",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./certs",
"target": "/certs",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
},
{
"name": "server",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "server",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"ports": [
"9000:9000",
"9443:9443"
],
"restart": "unless-stopped",
"shm_size": "512mb",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
}
],
"top_level": {
"name": "authentik",
"volumes": {
"database": {}
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"private_creation": "automatic-create-if-missing",
"private_address_allocation": "automatic-static-address-per-service",
"service_discovery": "private-addresses-with-compose-service-host-aliases",
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [
{
"id": "postgresql-volume-0",
"service": "postgresql",
"container_path": "/var/lib/postgresql/data",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "server-volume-0",
"service": "server",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "server-volume-1",
"service": "server",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
},
{
"id": "worker-volume-0",
"service": "worker",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "worker-volume-1",
"service": "worker",
"container_path": "/certs",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "worker-volume-2",
"service": "worker",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
}
],
"orchestration": {
"reserve_vmids_atomically": 3,
"start_order": [
"postgresql",
"worker",
"server"
],
"stop_order": [
"server",
"worker",
"postgresql"
],
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
},
"installer_inputs": {
"prompted": [
"stack_name",
"base_vmid",
"rootfs_storage",
"persistent_data_destinations",
"frontend_bridge",
"frontend_ipv4_mode"
],
"automatic": [
"dependent_vmids",
"private_bridge",
"private_subnet",
"private_service_addresses",
"compose_service_aliases",
"generated_secrets",
"dependency_start_and_stop_order"
],
"generated_secrets": [
{
"id": "pg-pass",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "postgresql",
"environment_variable": "POSTGRES_PASSWORD"
},
{
"service": "server",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
}
]
},
{
"id": "authentik-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "server",
"environment_variable": "AUTHENTIK_SECRET_KEY"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web interface",
"scheme": "http",
"port": 9000,
"path": "/",
"source": "official-compose"
}
],
"credentials": [
{
"label": "Initial administrator account",
"type": "static-default",
"username": "akadmin",
"password": null,
"change_required": true,
"source": "Authentik documentation; set the akadmin password during the first-run setup"
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 1024,
"swap_mb": 512,
"rootfs_size_gb": 8,
"rootfs_storage": "local-lvm",
"volume_storage": "local-lvm",
"template_storage": "local",
"bridge": "vmbr0",
"ipv4": "dhcp",
"firewall": true,
"host_managed_network": true,
"onboot": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 30
},
"image_metadata_policy": {
"entrypoint": "import-from-oci-image",
"cmd": "import-from-oci-image",
"environment": "import-image-env-then-apply-compose-overrides",
"user": "import-from-oci-image",
"working_dir": "import-from-oci-image",
"stop_signal": "import-from-oci-image"
},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/templates",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/certs",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/var/lib/postgresql/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"adaptations": [
{
"id": "worker-docker-socket-removed",
"upstream_behavior": "The official Compose mounts the Docker socket into the worker for automatic deployment and management of separate outposts.",
"native_lxc_behavior": "The Docker socket mount is omitted. Authentik itself runs as server, PostgreSQL and worker in native OCI LXCs.",
"reason": "The ProxMenux native OCI stack does not run Docker Engine or provide a Docker daemon socket.",
"behavioral_impact": "Use the embedded outpost for a simple proxy-provider deployment, or deploy and manage an outpost separately.",
"validation": "passed-ms03-2026-10-30"
},
{
"id": "postgres-healthcheck-normalized",
"upstream_behavior": "The official PostgreSQL healthcheck expands Compose environment variables in its command.",
"native_lxc_behavior": "The check uses the recipe’s fixed database and username with pg_isready against localhost.",
"reason": "The native stack healthcheck runs in the database LXC and does not evaluate Compose variable interpolation.",
"behavioral_impact": "It checks the same local database readiness before dependent services start.",
"validation": "passed-ms03-postgresql-readiness-2026-10-30"
},
{
"id": "server-worker-shared-data",
"upstream_behavior": "Authentik server and worker both mount /data and /templates from the same Compose source directories.",
"native_lxc_behavior": "ProxMenux uses shared host directories for these two paths across the server and worker LXCs.",
"reason": "The same data and custom templates must be visible to both services; the stack driver shares named Compose sources through host paths.",
"behavioral_impact": "These shared host directories are not included in native LXC backups and need a separate backup.",
"validation": "passed-ms03-2026-10-30"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"deploy",
"devices",
"entrypoint",
"environment",
"extra_hosts",
"group_add",
"healthcheck",
"hostname",
"image",
"init",
"ipc",
"labels",
"logging",
"mac_address",
"mem_limit",
"network_mode",
"networks",
"ports",
"privileged",
"restart",
"runtime",
"security_opt",
"shm_size",
"stdin_open",
"stop_grace_period",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Official Authentik Compose translated to three native OCI LXCs. The Docker socket mount is deliberately removed; the embedded outpost remains available."
},
"validation": {
"schema": "passed-local-2026-10-30",
"image_pull": "passed-ms03-skopeo-and-OCI-integrity-server-worker-postgresql-2026-10-30",
"default_install": "passed-ms03-three-LXCs-started-PostgreSQL-healthy-2026-10-30",
"advanced_install": "pending",
"credentials": "pending-user-first-run-and-login",
"web_ui": "pending-user-first-run-and-login",
"storage_modes": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"rollback": "pending",
"modification": "pending",
"removal": "pending",
"oci_engine_tests": "pending",
"offline_i18n_tests": "pending",
"runtime_architecture": "amd64-ms03; Skopeo manifests include amd64-and-arm64",
"database_health": "passed-pg_isready-2026-10-30"
},
"lifecycle": {
"update_strategy": "resolve-selected-tag-and-architecture-digest-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "2026.8.5",
"image_created": null
},
"change_detection": "compare-compose-sha256-and-resolved-image-digest",
"automatic_unattended_updates": false,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+645
View File
@@ -0,0 +1,645 @@
{
"schema_version": "0.5.0",
"kind": "proxmenux.oci-template",
"id": "image-authentik",
"status": "generated-unvalidated",
"catalog_ui": {
"title": {
"en_US": "Authentik"
},
"tagline": {
"en_US": "Identity provider for single sign-on and access control"
},
"description": {
"en_US": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC."
},
"category": "security",
"category_label": "Authentication & Security",
"author": "Authentik",
"developer": "Authentik",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"thumbnail": null,
"screenshots": [],
"architectures": [
"amd64",
"arm64"
],
"launch": {
"scheme": "http",
"port": 9000,
"path": "/"
},
"website": "https://goauthentik.io/",
"documentation": "https://docs.goauthentik.io/install-config/install/docker-compose/",
"repository": "https://github.com/goauthentik/authentik",
"tips": [
"The server and worker share /data and /templates through host directories; those directories are not included in Proxmox CT backups.",
"The worker Docker-socket mount is omitted. Use the embedded outpost or deploy and manage a separate outpost manually."
],
"mini_changelog": [],
"display_version": "2026.8.5",
"updated_at": "2026-10-21"
},
"source": {
"provider": "goauthentik",
"repository": "https://github.com/goauthentik/authentik",
"default_branch": "main",
"revision": "4ec0d19b98da3c329781894cd550da95d3033954",
"readme_raw_url": "https://goauthentik.io/version/2026.8/lifecycle/container/compose.yml",
"image_repository_url": "https://github.com/goauthentik/authentik/pkgs/container/server",
"readme_pushed_at": "2026-10-21T12:57:59Z",
"compose_sha256": "a3e138b5a85844d15c4a7ba6339997e43c0479d9d198582c67dd19523aadc694",
"generated_at": "2026-10-30T18:09:01+00:00"
},
"container_contract": {
"service_name": "server",
"container_name": "authentik-server",
"image": {
"reference": "ghcr.io/goauthentik/server:2026.8.5",
"registry": "ghcr.io",
"repository": "ghcr.io/goauthentik/server",
"tag": "2026.8.5",
"digest": null,
"pull_policy": "resolve-written-reference-to-architecture-digest-at-install"
},
"environment": [
{
"name": "AUTHENTIK_POSTGRESQL__HOST",
"example": "postgresql",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__NAME",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__PASSWORD",
"example": "${GENERATED_GENERATED_PG_PASS}",
"required": true,
"sensitive": true,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_POSTGRESQL__USER",
"example": "authentik",
"required": true,
"sensitive": false,
"source": "docker-compose"
},
{
"name": "AUTHENTIK_SECRET_KEY",
"example": "${GENERATED_GENERATED_AUTHENTIK_SECRET_KEY}",
"required": true,
"sensitive": true,
"source": "docker-compose"
}
],
"volumes": [
{
"id": "volume-0",
"container_path": "/data",
"compose_source_example": "./data",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
},
{
"id": "volume-1",
"container_path": "/templates",
"compose_source_example": "./custom-templates",
"read_only": false,
"required": true,
"installation_choice": [
"host-bind"
],
"default": "host-bind",
"managed_volume": {
"backup": false,
"default_size_gb": 8
}
}
],
"ports": [
{
"container_port": 9000,
"published_example": 9000,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
},
{
"container_port": 9443,
"published_example": 9443,
"protocol": "tcp",
"required": true,
"proxmox_behavior": "listener-on-dedicated-lxc-address-no-nat"
}
],
"related_services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine"
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5"
}
],
"restart": "unless-stopped",
"stop_grace_period": null,
"original_compose": "name: authentik\nservices:\n postgresql:\n environment:\n POSTGRES_DB: authentik\n POSTGRES_PASSWORD: ${GENERATED_PG_PASS}\n POSTGRES_USER: authentik\n healthcheck:\n interval: 30s\n retries: 5\n start_period: 20s\n test:\n - CMD-SHELL\n - pg_isready -h 127.0.0.1 -d authentik -U authentik\n timeout: 5s\n image: docker.io/library/postgres:16-alpine\n restart: unless-stopped\n volumes:\n - read_only: false\n source: database\n target: /var/lib/postgresql/data\n server:\n command: server\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n ports:\n - 9000:9000\n - 9443:9443\n restart: unless-stopped\n shm_size: 512mb\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./custom-templates\n target: /templates\n worker:\n command: worker\n depends_on:\n postgresql:\n condition: service_healthy\n environment:\n AUTHENTIK_POSTGRESQL__HOST: postgresql\n AUTHENTIK_POSTGRESQL__NAME: authentik\n AUTHENTIK_POSTGRESQL__PASSWORD: ${GENERATED_PG_PASS}\n AUTHENTIK_POSTGRESQL__USER: authentik\n AUTHENTIK_SECRET_KEY: ${GENERATED_AUTHENTIK_SECRET_KEY}\n image: ghcr.io/goauthentik/server:2026.8.5\n restart: unless-stopped\n shm_size: 512mb\n user: root\n volumes:\n - read_only: false\n source: ./data\n target: /data\n - read_only: false\n source: ./certs\n target: /certs\n - read_only: false\n source: ./custom-templates\n target: /templates\nvolumes:\n database: {}\n"
},
"compose_stack": {
"project_name": "authentik",
"deployment_model": "one-native-oci-lxc-per-compose-service",
"user_experience": "single-application-install",
"main_service": "server",
"service_count": 3,
"services": [
{
"name": "postgresql",
"image": "docker.io/library/postgres:16-alpine",
"is_main": false,
"role": "dependency",
"vmid_offset": 1,
"depends_on": [],
"frontend_network": false,
"private_network": true,
"compose": {
"environment": {
"POSTGRES_DB": "authentik",
"POSTGRES_PASSWORD": "${GENERATED_PG_PASS}",
"POSTGRES_USER": "authentik"
},
"healthcheck": {
"interval": "30s",
"retries": 5,
"start_period": "20s",
"test": [
"CMD-SHELL",
"pg_isready -h 127.0.0.1 -d authentik -U authentik"
],
"timeout": "5s"
},
"image": "docker.io/library/postgres:16-alpine",
"restart": "unless-stopped",
"volumes": [
{
"source": "database",
"target": "/var/lib/postgresql/data",
"read_only": false
}
]
}
},
{
"name": "worker",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": false,
"role": "dependency",
"vmid_offset": 2,
"depends_on": [
"postgresql"
],
"frontend_network": false,
"private_network": true,
"compose": {
"command": "worker",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"restart": "unless-stopped",
"shm_size": "512mb",
"user": "root",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./certs",
"target": "/certs",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
},
{
"name": "server",
"image": "ghcr.io/goauthentik/server:2026.8.5",
"is_main": true,
"role": "frontend",
"vmid_offset": 0,
"depends_on": [
"postgresql"
],
"frontend_network": true,
"private_network": true,
"compose": {
"command": "server",
"depends_on": {
"postgresql": {
"condition": "service_healthy"
}
},
"environment": {
"AUTHENTIK_POSTGRESQL__HOST": "postgresql",
"AUTHENTIK_POSTGRESQL__NAME": "authentik",
"AUTHENTIK_POSTGRESQL__PASSWORD": "${GENERATED_PG_PASS}",
"AUTHENTIK_POSTGRESQL__USER": "authentik",
"AUTHENTIK_SECRET_KEY": "${GENERATED_AUTHENTIK_SECRET_KEY}"
},
"image": "ghcr.io/goauthentik/server:2026.8.5",
"ports": [
"9000:9000",
"9443:9443"
],
"restart": "unless-stopped",
"shm_size": "512mb",
"volumes": [
{
"source": "./data",
"target": "/data",
"read_only": false
},
{
"source": "./custom-templates",
"target": "/templates",
"read_only": false
}
]
}
}
],
"top_level": {
"name": "authentik",
"volumes": {
"database": {}
}
},
"networking": {
"frontend": "selected-proxmox-bridge",
"private_required": true,
"private_creation": "automatic-create-if-missing",
"private_address_allocation": "automatic-static-address-per-service",
"service_discovery": "private-addresses-with-compose-service-host-aliases",
"dependency_external_access": "disabled-unless-service-publishes-ports",
"prompt_user_for_private_network": false
},
"storage": [
{
"id": "postgresql-volume-0",
"service": "postgresql",
"container_path": "/var/lib/postgresql/data",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "server-volume-0",
"service": "server",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "server-volume-1",
"service": "server",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
},
{
"id": "worker-volume-0",
"service": "worker",
"container_path": "/data",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/data"
},
{
"id": "worker-volume-1",
"service": "worker",
"container_path": "/certs",
"mode": "managed-volume",
"user_selectable": false,
"backup": true,
"shared_with_other_lxc": false,
"source_path": null,
"source_path_prompt": null
},
{
"id": "worker-volume-2",
"service": "worker",
"container_path": "/templates",
"mode": "host-bind",
"user_selectable": true,
"backup": false,
"shared_with_other_lxc": true,
"source_path": null,
"source_path_prompt": "Host directory for authentik:/templates"
}
],
"orchestration": {
"reserve_vmids_atomically": 3,
"start_order": [
"postgresql",
"worker",
"server"
],
"stop_order": [
"server",
"worker",
"postgresql"
],
"dependency_readiness": "compose-healthcheck-then-port-or-process-fallback",
"rollback_on_failure": "remove-new-rootfs-preserve-created-persistent-volumes"
},
"installer_inputs": {
"prompted": [
"stack_name",
"base_vmid",
"rootfs_storage",
"persistent_data_destinations",
"frontend_bridge",
"frontend_ipv4_mode"
],
"automatic": [
"dependent_vmids",
"private_bridge",
"private_subnet",
"private_service_addresses",
"compose_service_aliases",
"generated_secrets",
"dependency_start_and_stop_order"
],
"generated_secrets": [
{
"id": "pg-pass",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "postgresql",
"environment_variable": "POSTGRES_PASSWORD"
},
{
"service": "server",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_POSTGRESQL__PASSWORD"
}
]
},
{
"id": "authentik-secret-key",
"strategy": "generate-cryptographically-random-at-install",
"bindings": [
{
"service": "server",
"environment_variable": "AUTHENTIK_SECRET_KEY"
},
{
"service": "worker",
"environment_variable": "AUTHENTIK_SECRET_KEY"
}
]
}
]
}
},
"first_run": {
"endpoints": [
{
"label": "Web interface",
"scheme": "http",
"port": 9000,
"path": "/",
"source": "official-compose"
}
],
"credentials": [
{
"label": "Initial administrator account",
"type": "static-default",
"username": "akadmin",
"password": null,
"change_required": true,
"source": "Authentik documentation; set the akadmin password during the first-run setup"
}
]
},
"proxmox": {
"runtime": "native-oci-lxc",
"technology_status": "proxmox-technology-preview",
"defaults": {
"unprivileged": true,
"ostype": "auto-from-image",
"cores": 2,
"memory_mb": 1024,
"swap_mb": 512,
"rootfs_size_gb": 8,
"rootfs_storage": "local-lvm",
"volume_storage": "local-lvm",
"template_storage": "local",
"bridge": "vmbr0",
"ipv4": "dhcp",
"firewall": true,
"host_managed_network": true,
"onboot": true,
"features": [
"nesting=1"
],
"shutdown_timeout_seconds": 30
},
"image_metadata_policy": {
"entrypoint": "import-from-oci-image",
"cmd": "import-from-oci-image",
"environment": "import-image-env-then-apply-compose-overrides",
"user": "import-from-oci-image",
"working_dir": "import-from-oci-image",
"stop_signal": "import-from-oci-image"
},
"installer_profile": {
"stack_driver": "generic-multi-lxc-stack",
"volume_preparations": [
{
"container_path": "/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/templates",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/certs",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
},
{
"container_path": "/var/lib/postgresql/data",
"remove_lost_found": true,
"owner_strategy": "mapped-application-user",
"only_when_mount_type": "managed-volume"
}
]
},
"adaptations": [
{
"id": "worker-docker-socket-removed",
"upstream_behavior": "The official Compose mounts the Docker socket into the worker for automatic deployment and management of separate outposts.",
"native_lxc_behavior": "The Docker socket mount is omitted. Authentik itself runs as server, PostgreSQL and worker in native OCI LXCs.",
"reason": "The ProxMenux native OCI stack does not run Docker Engine or provide a Docker daemon socket.",
"behavioral_impact": "Use the embedded outpost for a simple proxy-provider deployment, or deploy and manage an outpost separately.",
"validation": "passed-ms03-2026-10-30"
},
{
"id": "postgres-healthcheck-normalized",
"upstream_behavior": "The official PostgreSQL healthcheck expands Compose environment variables in its command.",
"native_lxc_behavior": "The check uses the recipe’s fixed database and username with pg_isready against localhost.",
"reason": "The native stack healthcheck runs in the database LXC and does not evaluate Compose variable interpolation.",
"behavioral_impact": "It checks the same local database readiness before dependent services start.",
"validation": "passed-ms03-postgresql-readiness-2026-10-30"
},
{
"id": "server-worker-shared-data",
"upstream_behavior": "Authentik server and worker both mount /data and /templates from the same Compose source directories.",
"native_lxc_behavior": "ProxMenux uses shared host directories for these two paths across the server and worker LXCs.",
"reason": "The same data and custom templates must be visible to both services; the stack driver shares named Compose sources through host paths.",
"behavioral_impact": "These shared host directories are not included in native LXC backups and need a separate backup.",
"validation": "passed-ms03-2026-10-30"
}
],
"custom_compose_stack": true,
"custom_compose_interpolated": false,
"stack_image_entrypoints": {}
},
"compatibility": {
"automatic_install_candidate": true,
"validated": false,
"supported_compose_keys": [
"cap_add",
"command",
"container_name",
"cpu_shares",
"deploy",
"devices",
"entrypoint",
"environment",
"extra_hosts",
"group_add",
"healthcheck",
"hostname",
"image",
"init",
"ipc",
"labels",
"logging",
"mac_address",
"mem_limit",
"network_mode",
"networks",
"ports",
"privileged",
"restart",
"runtime",
"security_opt",
"shm_size",
"stdin_open",
"stop_grace_period",
"stop_signal",
"sysctls",
"tty",
"ulimits",
"user",
"volumes",
"working_dir"
],
"untranslated_blockers": [],
"policy": "Official Authentik Compose translated to three native OCI LXCs. The Docker socket mount is deliberately removed; the embedded outpost remains available."
},
"validation": {
"schema": "passed-local-2026-10-30",
"image_pull": "passed-ms03-skopeo-and-OCI-integrity-server-worker-postgresql-2026-10-30",
"default_install": "passed-ms03-three-LXCs-started-PostgreSQL-healthy-2026-10-30",
"advanced_install": "pending",
"credentials": "pending-user-first-run-and-login",
"web_ui": "pending-user-first-run-and-login",
"storage_modes": "pending",
"restart_persistence": "pending",
"backup_restore": "pending",
"update_preserves_data": "pending",
"rollback": "pending",
"modification": "pending",
"removal": "pending",
"oci_engine_tests": "pending",
"offline_i18n_tests": "pending",
"runtime_architecture": "amd64-ms03; Skopeo manifests include amd64-and-arm64",
"database_health": "passed-pg_isready-2026-10-30"
},
"lifecycle": {
"update_strategy": "resolve-selected-tag-and-architecture-digest-then-apply-reviewed-native-lxc-update",
"registry_state": {
"resolved_architecture": null,
"resolved_digest": null,
"image_version_label": "2026.8.5",
"image_created": null
},
"change_detection": "compare-compose-sha256-and-resolved-image-digest",
"automatic_unattended_updates": false,
"dependency_lifecycle": {
"implementation": "proxmox-hookscript",
"trigger": "main-lxc-pre-start",
"waits_for_dependency_healthchecks": true,
"stops_dependencies_with_main": false
}
}
}
+35 -2
View File
@@ -2,7 +2,7 @@
"schema_version": "0.2.0",
"kind": "proxmenux.oci-catalog-index",
"provider": "multiple-container-images",
"generated_at": "2026-10-26T08:05:54+00:00",
"generated_at": "2026-10-30T18:13:36+00:00",
"applications": [
{
"id": "2fauth",
@@ -752,6 +752,39 @@
"hidden": false,
"template_id": "image-audiobookshelf"
},
{
"id": "authentik",
"provider": "goauthentik",
"template_family": "curated-profile",
"title": "Authentik",
"repository": "https://github.com/goauthentik/authentik",
"description": "Authentik is an open-source identity provider and access platform. This recipe follows the official three-service deployment with PostgreSQL, the server and a worker, each in its own Proxmox OCI LXC.",
"website": "https://goauthentik.io/",
"icon": "https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/authentik.webp",
"architectures": [
"amd64",
"arm64"
],
"updated_at": "2026-10-21",
"main_image": "ghcr.io/goauthentik/server:2026.8.5",
"category": "security",
"category_label": "Authentication & Security",
"replaces_discovered_ids": [],
"curated_path": "catalog/curated/authentik.json",
"template_id": "image-authentik",
"template": "apps/authentik.json",
"template_status": "generated-unvalidated",
"hidden": false,
"automatic_install_candidate": true,
"untranslated_blockers": [],
"requires_privileged_lxc": false,
"optional_privileged_lxc": false,
"requires_host_pid_namespace": false,
"requires_relaxed_confinement": false,
"optional_relaxed_confinement": false,
"requires_security_confirmation": false,
"multi_container": true
},
{
"id": "autobrr",
"source_app_id": "autobrr",
@@ -18976,7 +19009,7 @@
"skipped": []
},
"curated_profiles": {
"included_count": 17
"included_count": 18
}
}
}
+143
View File
@@ -0,0 +1,143 @@
"""Authentik keeps the vendor's three-service deployment as native OCI LXCs."""
import json
from pathlib import Path
import unittest
ROOT = Path(__file__).resolve().parents[1]
class AuthentikRecipeTests(unittest.TestCase):
def setUp(self):
self.curated = json.loads(
(ROOT / "catalog/curated/authentik.json").read_text(encoding="utf-8")
)
self.generated = json.loads(
(ROOT / "catalog/apps/authentik.json").read_text(encoding="utf-8")
)
self.services = {
service["name"]: service
for service in self.curated["compose_stack"]["services"]
}
def test_generated_catalog_copy_matches_curated_profile(self):
self.assertEqual(self.curated, self.generated)
def test_official_three_service_stack_uses_pinned_images_and_order(self):
self.assertEqual(set(self.services), {"postgresql", "server", "worker"})
self.assertEqual(self.curated["compose_stack"]["main_service"], "server")
self.assertEqual(
self.services["server"]["image"], "ghcr.io/goauthentik/server:2026.8.5"
)
self.assertEqual(
self.services["worker"]["image"], "ghcr.io/goauthentik/server:2026.8.5"
)
self.assertEqual(
self.services["postgresql"]["image"],
"docker.io/library/postgres:16-alpine",
)
self.assertEqual(self.services["server"]["depends_on"], ["postgresql"])
self.assertEqual(self.services["worker"]["depends_on"], ["postgresql"])
def test_generated_database_and_application_secrets_are_shared_correctly(self):
services = {
name: service["compose"].get("environment", {})
for name, service in self.services.items()
}
self.assertEqual(
services["postgresql"]["POSTGRES_PASSWORD"], "${GENERATED_PG_PASS}"
)
for name in ("server", "worker"):
self.assertEqual(
services[name]["AUTHENTIK_POSTGRESQL__PASSWORD"],
"${GENERATED_PG_PASS}",
)
self.assertEqual(
services[name]["AUTHENTIK_SECRET_KEY"],
"${GENERATED_AUTHENTIK_SECRET_KEY}",
)
generated = self.curated["compose_stack"]["installer_inputs"][
"generated_secrets"
]
self.assertEqual({secret["id"] for secret in generated}, {
"pg-pass",
"authentik-secret-key",
})
def test_persistent_mounts_and_database_healthcheck_are_preserved(self):
mounts = {
name: service["compose"].get("volumes", [])
for name, service in self.services.items()
}
self.assertIn(
{"source": "database", "target": "/var/lib/postgresql/data", "read_only": False},
mounts["postgresql"],
)
self.assertIn(
{"source": "./data", "target": "/data", "read_only": False},
mounts["server"],
)
self.assertIn(
{"source": "./data", "target": "/data", "read_only": False},
mounts["worker"],
)
self.assertIn(
{"source": "./custom-templates", "target": "/templates", "read_only": False},
mounts["server"],
)
self.assertIn(
{"source": "./custom-templates", "target": "/templates", "read_only": False},
mounts["worker"],
)
self.assertIn(
{"source": "./certs", "target": "/certs", "read_only": False},
mounts["worker"],
)
check = self.services["postgresql"]["compose"]["healthcheck"]["test"]
self.assertEqual(
check,
["CMD-SHELL", "pg_isready -h 127.0.0.1 -d authentik -U authentik"],
)
def test_cross_lxc_data_and_templates_are_host_shared_and_backed_up_separately(self):
shared = [
item
for item in self.curated["compose_stack"]["storage"]
if item["container_path"] in ("/data", "/templates")
]
self.assertEqual(len(shared), 4)
self.assertTrue(all(item["mode"] == "host-bind" for item in shared))
self.assertTrue(all(item["shared_with_other_lxc"] for item in shared))
self.assertTrue(all(item["backup"] is False for item in shared))
self.assertTrue(
all(
volume["default"] == "host-bind"
and volume["managed_volume"]["backup"] is False
for volume in self.curated["container_contract"]["volumes"]
)
)
def test_docker_socket_is_not_mounted_and_both_web_ports_are_exposed(self):
all_mounts = [
mount
for service in self.services.values()
for mount in service["compose"].get("volumes", [])
]
self.assertFalse(any("docker.sock" in mount for mount in all_mounts))
self.assertEqual(
self.services["server"]["compose"]["ports"],
["9000:9000", "9443:9443"],
)
def test_first_run_discloses_the_initial_admin_setup_without_faking_a_password(self):
first_run = self.curated["first_run"]
self.assertEqual(first_run["endpoints"][0]["port"], 9000)
credential = first_run["credentials"][0]
self.assertEqual(credential["username"], "akadmin")
self.assertIsNone(credential["password"])
self.assertTrue(credential["change_required"])
if __name__ == "__main__":
unittest.main()