feat(templates): remote template registry via CONTAINR_TEMPLATE_REGISTRY_URL (#52)

- GET /templates merges a remote Containr catalog as source:"registry"
  (5s timeout, 60s cache, offline-safe)
- GET /templates/:id falls through to the registry
- Deploy of a registry template materializes a deterministic private
  copy for the deployer — repeat deploys reuse it
- Templates page badges registry entries; openapi + env documented

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomáš DvořákandDevin authored and GitHub committed 2026-10-09 12:46:35 +02:00
1 parent 3f066b3b1e
commit e054457156
8 files changed
+253 -18

No files matched your search

@@ -644,6 +644,11 @@ export function TemplatesPage() {
Public
</span>
) : null}
{template.source === 'registry' ? (
<span className="px-1.5 py-0.5 rounded bg-[var(--accent-primary-soft)] text-[var(--accent-primary)] text-[10px] font-semibold">
Registry
</span>
) : null}
</div>
<div className="flex items-center gap-2 mt-1">
<span
+2
View File
@@ -11094,6 +11094,8 @@ export interface components {
owner_id?: string | null;
/** @description Whether a user-created template is published to the public catalog */
is_public?: boolean;
/** @description Catalog origin: absent for local/official templates; "registry" for entries merged from CONTAINR_TEMPLATE_REGISTRY_URL. Deploying a registry template materializes a local copy first. */
source?: string;
/**
* Format: date-time
* @description Creation timestamp
+2
View File
@@ -181,6 +181,7 @@ export type TemplateEntity = {
isOfficial: boolean;
ownerId: string | null;
isPublic: boolean;
source?: string;
createdAt?: string;
updatedAt?: string;
};
@@ -751,6 +752,7 @@ function normalizeTemplate(template: RawServiceTemplate): TemplateEntity | null
isOfficial: Boolean(template.is_official),
ownerId: template.owner_id ?? null,
isPublic: Boolean(template.is_public),
source: template.source,
createdAt: template.created_at,
updatedAt: template.updated_at,
};