A Cloudflare Worker that aggregates multiple Portainer V2 application template files into a single, de-duplicated, and sorted JSON feed for your Portainer instance.
- Aggregates Multiple Sources: Combines templates from various JSON feeds.
- De-duplicates: Removes duplicate templates based on title (case-insensitive).
- Sorts: Sorts the final list of templates alphabetically by title.
- Dynamic User Sources: Allows adding custom template sources via a URL query parameter.
- Self-Hosted & Free: Runs on a free Cloudflare Workers plan.
- Enhanced Error Handling & Logging: Improved resilience when fetching and parsing template sources.
This project was created because the original GitLab snippet by Ibaraki (and similar solutions) faced some issues with newer Portainer versions and some template sources became outdated or returned errors. This worker aims to:
- Provide a more robust solution for aggregating Portainer templates.
- Use an updated list of default template sources.
- Implement more resilient fetching and parsing logic.
- Offer clearer logging and diagnostics via Cloudflare Worker logs and response headers.
I needed a reliable way to manage a large and diverse set of Portainer templates for my self-hosting setup. The original sources often weren't updated.
The Cloudflare Worker fetches template definitions from:
- A predefined list of default URLs in
src/index.js. - Any URLs provided in a
?templates=query parameter (Base64 encoded JSON array of URLs). - (Optionally) Template objects hardcoded directly into
src/index.js.
It then:
- Parses each source.
- Extracts individual template objects.
- Filters out malformed or incomplete templates.
- Merges all valid templates into a single list.
- Removes duplicates by title (case-insensitive).
- Sorts the list alphabetically by title.
- Returns a Portainer V2 compatible JSON response.
This worker aggregates a wide variety of application templates from multiple sources. Based on the default configuration, it can typically fetch and de-duplicate several hundred unique app templates!
π View the current list of aggregated app titles (app_titles.txt)
Note: This list is based on app titles successfully processed by the worker using the default sources at a specific point in time. The actual number and specific apps available through your deployed worker will vary depending on the current status and content of the upstream template sources defined in your src/index.js and any dynamic URLs you provide.
Got a great Portainer V2 template list (a URL pointing to a valid JSON file) that you think should be included in the default set for everyone using this worker? The easiest way to suggest it is to:
- Open an Issue on this GitHub repository (
https://github.com/niyisurvey/pt-template-aggregator/issues). - In the issue, please provide:
- A direct link to the raw JSON template file URL.
- A brief reason why you think it's a good addition.
I'll review suggestions and consider adding them to the defaultTemplateProviderURLs in src/index.js in future updates!
- A Cloudflare account.
wranglerCLI installed (npm install -g wrangler).
- Clone this repository:
git clone [https://github.com/niyisurvey/pt-template-aggregator.git](https://github.com/niyisurvey/pt-template-aggregator.git) # Replace with your actual repository URL if different cd pt-template-aggregator # Or your actual project folder name, e.g., fresh-portainer-worker-cf
- Log in to Cloudflare (if needed):
wrangler login
- Deploy the worker:
This will output your worker URL (e.g.,
wrangler deploy
https://fresh-portainer-worker-cf.your-account.workers.dev). Note this URL.
- In Portainer, navigate to Settings > App Templates.
- Paste your deployed worker URL into the "URL" field.
- Click Save application settings.
There are a few ways to add your own custom Portainer templates or lists of templates to this worker:
1. For the Worker Owner: Modifying the Worker Code (src/index.js)
This method is best if you are hosting the worker yourself and want to permanently include specific templates or template lists. After making changes to src/index.js, you must re-deploy the worker using wrangler deploy.
-
A. Adding URLs to External Template Files:
- Open the
src/index.jsfile in your project. - Locate the
defaultTemplateProviderURLsarray. - Add the direct URLs to your JSON template files to this array. For example:
const defaultTemplateProviderURLs = [ "[https://raw.githubusercontent.com/portainer/templates/master/templates.json](https://raw.githubusercontent.com/portainer/templates/master/templates.json)", // ... other default URLs ... "[https://your-url.com/path/to/your-custom-templates.json](https://your-url.com/path/to/your-custom-templates.json)", // Add your URL here "[https://gist.githubusercontent.com/yourusername/yourgistid/raw/your-templates.json](https://gist.githubusercontent.com/yourusername/yourgistid/raw/your-templates.json)" // Example Gist URL ];
- Open the
-
B. Adding Individual Template Objects Directly (for a few custom templates):
- If you have only a few simple templates and don't want to host a separate JSON file, you can add them directly into the
src/index.jsscript. - Find the line
let aggregatedTemplates = [];(near the beginning of theWorkspacefunction). - You can
pushtemplate objects directly into this array. Example:let aggregatedTemplates = []; aggregatedTemplates.push({ "type": 1, // 1 for container, 2 for Swarm stack, 3 for Compose stack "title": "My Custom App (Built-in)", "name": "my-custom-app-stack", // For stacks, this is the stack name "description": "A brief description of my awesome custom application.", "categories": ["Custom", "Utility"], "platform": "linux", // or "windows" "logo": "[https://example.com/path/to/your/logo.png](https://example.com/path/to/your/logo.png)", // Optional but recommended "image": "yourdockerimage/yourcustomapp:latest", // For type 1 (container) // For type 2 or 3 (stacks), you'd use 'repository' instead of 'image': // "repository": { // "url": "[https://github.com/yourusername/your-app-stack-repo](https://github.com/yourusername/your-app-stack-repo)", // "stackfile": "docker-compose.yml" // or portainer-stack.yml etc. // }, "env": [ // Optional environment variables { "name": "MY_VARIABLE", "label": "My Custom Variable", "default": "default_value" } ], "ports": ["8080:80/tcp"], // Optional port mappings "note": "Some notes about this custom template." // Optional notes (can be HTML) });
- Ensure your template object follows the Portainer V2 template schema. You can find more details by looking at the structure of Portainer's official template file.
- If you have only a few simple templates and don't want to host a separate JSON file, you can add them directly into the
2. For Any User (including the Worker Owner): Using the URL Parameter
This method allows you or anyone using your deployed worker URL to dynamically add template lists without modifying the worker's code.
-
A. Create Your Template JSON File:
- Your JSON file must be an array of Portainer V2 template objects, OR an object containing a
"templates"key with such an array (like Portainer's official file). - Example
my-templates.jsonstructure (an array of templates):[ { "type": 1, "title": "My App 1 via URL Param", "name": "myapp1-url", "description": "Description for My App 1.", "image": "nginx:latest", "logo": "[https://raw.githubusercontent.com/portainer/templates/master/logos/nginx.png](https://raw.githubusercontent.com/portainer/templates/master/logos/nginx.png)", "ports": ["8001:80/tcp"] }, { "type": 1, "title": "My App 2 via URL Param", "name": "myapp2-url", "description": "Description for My App 2.", "image": "httpd:latest", "logo": "[https://raw.githubusercontent.com/portainer/templates/master/logos/httpd.png](https://raw.githubusercontent.com/portainer/templates/master/logos/httpd.png)", "ports": ["8002:80/tcp"] } ]
- Your JSON file must be an array of Portainer V2 template objects, OR an object containing a
-
B. Host Your JSON File Publicly:
- Upload your
my-templates.jsonfile to a place where it's accessible via a direct public URL. Good free options include:- GitHub Gist: Create a public Gist, name the file (e.g.,
my_templates.json), paste your JSON content, save it, and then click the "Raw" button to get the direct URL. - GitHub Repository: Add the JSON file to a public GitHub repository and use the "Raw" file URL (navigate to the file, click "Raw").
- GitHub Gist: Create a public Gist, name the file (e.g.,
- Upload your
-
C. Prepare the URL for Portainer:
- Create a JSON array string containing the raw URL(s) pointing to your template file(s):
["https://your-raw-url.com/path/to/my-templates.json"](You can include multiple URLs in this array, separated by commas). - Convert this entire JSON array string to Base64. You can use an online tool (search for "json to base64 encoder") or a script.
Example: If your JSON array string is
["https://example.com/templates.json"], the Base64 might beWyJodHRwczovL2V4YW1wbGUuY29tL3RlbXBsYXRlcy5qc29uIl0=. - Take your deployed worker URL (e.g.,
https://fresh-portainer-worker-cf.niyi-666.workers.dev) and append thetemplatesquery parameter with your Base64 string:https://fresh-portainer-worker-cf.niyi-666.workers.dev/?templates=YOUR_BASE64_ENCODED_STRING_HERE - Use this complete URL in Portainer's App Templates settings.
- Create a JSON array string containing the raw URL(s) pointing to your template file(s):
This worker is configured by default in src/index.js to attempt to fetch templates from the following community and official sources. This list has been curated based on known template providers; you can further customize this list in src/index.js to your preference. Please note that the availability and content of these external URLs can change, and some may become outdated or produce errors over time. Always verify sources.
- Official Portainer:
https://raw.githubusercontent.com/portainer/templates/master/templates.json - Lissy93:
https://raw.githubusercontent.com/Lissy93/portainer-templates/main/templates.json(Aggregated list) - xneo1:
https://raw.githubusercontent.com/xneo1/portainer_templates/master/Template/template.json - technorabilia:
https://raw.githubusercontent.com/technorabilia/portainer-templates/main/lsio/templates/templates.json - Qballjos:
https://raw.githubusercontent.com/Qballjos/portainer_templates/master/Template/template.json - TheLustriVA:
https://raw.githubusercontent.com/TheLustriVA/portainer-templates-Nov-2022-collection/main/templates_2_2_rc_2_2.json - ntv-one:
https://raw.githubusercontent.com/ntv-one/portainer/main/template.json - mycroftwilde:
https://raw.githubusercontent.com/mycroftwilde/portainer_templates/master/Template/template.json - mikestraney:
https://raw.githubusercontent.com/mikestraney/portainer-templates/master/templates.json - dnburgess:
https://raw.githubusercontent.com/dnburgess/self-hosted-template/master/template.json - SelfhostedPro:
https://raw.githubusercontent.com/SelfhostedPro/selfhosted_templates/portainer-2.0/Template/template.json - mediadepot:
https://raw.githubusercontent.com/mediadepot/templates/master/portainer.json - novaspirit:
https://raw.githubusercontent.com/novaspirit/pi-hosted/master/pi-hosted_template/template/portainer-v2.json - shmolf:
https://raw.githubusercontent.com/shmolf/portainer-templates/main/templates-2.0.json
You can modify the defaultTemplateProviderURLs array in src/index.js to add, remove, or change these sources, then re-deploy the worker.
- This project is an evolution of the concept originally shared by Ibaraki on GitLab:
https://git.ibaraki.app/-/snippets/3(Original CF Worker for dynamic portainer templates). Thank you for the initial idea! - Inspired by discussions and issues reported by the community on various forums regarding Portainer custom templates.
- Uses template sources provided by the Portainer team and various community members (as listed above), whose contributions to the self-hosting community are greatly appreciated.
Compared to the original GitLab snippet or other available solutions, this version aims to:
- Update Source URLs: The official Portainer template URL was updated, and a comprehensive list of community sources has been included by default.
- Improve Error Handling: More resilient fetching, better handling of non-JSON responses (like
text/plainfrom GitHub raw links), and timeout management. - Enhance Logging: Added more detailed
console.logstatements for easier debugging via Cloudflare Worker logs. - Diagnostic Headers: Implemented
X-Worker-*response headers to provide a summary of fetched sources and processed templates. - Code Structure: Refactored the worker script for better readability and maintenance.
- Favicon Handling: Added specific ignoring of
/favicon.icorequests to keep logs cleaner.
The primary motivation was that some older solutions weren't working reliably due to changes in Portainer, template source availability/format, and error handling limitations. This version attempts to address those issues by incorporating a wider range of sources and more robust processing.
This project is provided as is. You are free to use, modify, and distribute it. A mention though wouldn't go amiss π₯Ή