Prevent database corruption from duplicate Docker label resource names across sites #675

Closed
opened 2026-04-05 17:32:51 +02:00 by MrUnknownDE · 0 comments
Owner

Originally created by @Copilot on 11/4/2025

When multiple Newt instances use identical resource names in Docker labels (e.g., pangolin.proxy-resources.syncthing on host1 and host2), they overwrite the same database record, nulling fields and causing service outages.

Changes

Duplicate detection in blueprint application

  • Check if resource niceId exists with targets/config on different siteId before update
  • Throw on cross-site name collision with actionable error message
  • Allow same-site updates (multiple containers sharing one resource on same host)

Error handling

  • Surface site names in conflict and suggest unique naming (e.g., syncthing-host1)
  • Propagate errors to Newt instances via existing blueprint result channel

Implementation details

  • proxyResources.ts: Query existing targets by siteId, validate against update source
  • clientResources.ts: Validate existingResource.siteId !== site.siteId before update
  • Use inArray for efficient multi-site lookups

Example error

Duplicate resource name 'syncthing': already in use on site [host1]. 
Use unique name on site [host2]. Suggestion: 'syncthing-host2'

Previously this scenario corrupted the database silently. Now it fails fast with clear remediation.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • scarf.sh
    • Triggering command: node ./report.js (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>Duplicate docker labels breaking Pangolin</issue_title>
<issue_description>### Describe the Bug

I'm trying out Pangolin and want to get away from the hidden database-method of configuring things, I want everything with docker labels - enter blueprints.

Unfortunately when you do a configuration mistake and create duplicate names, all docker labels configured resources are deleted in the database, more specifically: subDomain is nulled and fullDomain is set to the base domain.

Environment

OS Type & Version: Debian & Ubuntu
Pangolin Version: 1.11.0
Gerbil Version: 1.2.1
Traefik Version: v3.5.3
Newt Version: 1.5.2

To Reproduce

  1. Have host1 & host2
  2. Configure host1 as follows:
labels:
      pangolin.proxy-resources.syncthing.name: syncthing
      pangolin.proxy-resources.syncthing.full-domain: syncthing-host1.domain.example.org
      pangolin.proxy-resources.syncthing.protocol: http
      pangolin.proxy-resources.syncthing.auth.sso-enabled: "true"
      pangolin.proxy-resources.syncthing.targets[0].method: http
      pangolin.proxy-resources.syncthing.targets[0].hostname: syncthing
      pangolin.proxy-resources.syncthing.targets[0].port: 8384
  1. Configure host2 as follows
labels:
      pangolin.proxy-resources.syncthing.name: syncthing
      pangolin.proxy-resources.syncthing.full-domain: syncthing-host2.domain.example.org
      pangolin.proxy-resources.syncthing.protocol: http
      pangolin.proxy-resources.syncthing.auth.sso-enabled: "true"
      pangolin.proxy-resources.syncthing.targets[0].method: http
      pangolin.proxy-resources.syncthing.targets[0].hostname: syncthing
      pangolin.proxy-resources.syncthing.targets[0].port: 8384
  1. Restart newt on host1 & host2. There are no errors in either newt log and no errors in the Pangolin log
  2. Everything is still working
  3. Restart Pangolin on host0 - no errors in the log.
  4. Observe that newt on host1&host2 are throwing errors:
ERROR: 2025/10/20 09:55:23 Failed to connect: failed to get token: failed to request new token: Post "https://cloud.gaida.biz/auth/resource/bdfd60bc-0193-409e-8a97-4bb0500b61ff?redirect=https%3A%2F%2Fsubdomain.example.com%2Fauth%2Fresource%2Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%3Fredirect%3Dhttps%253A%252F%252Fsubdomain.example.com%252Fauth%252Fresource%252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%253Fredirect%253Dhttps%25253A%25252F%25252Fsubdomain.example.com%25252Fauth%25252Fresource%25252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25253Fredirect%25253Dhttps%2525253A%2525252F%2525252Fsubdomain.example.com%2525252Fauth%2525252Fresource%2525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%2525253Fredirect%2525253Dhttps%252525253A%252525252F%252525252Fsubdomain.example.com%252525252Fauth%252525252Fresource%252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%252525253Fredirect%252525253Dhttps%25252525253A%25252525252F%25252525252Fsubdomain.example.com%25252525252Fauth%25252525252Fresource%25252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25252525253Fredirect%25252525253Dhttps%2525252525253A%2525252525252F%2525252525252Fsubdomain.example.com%2525252525252Fauth%2525252525252Fresource%2525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%2525252525253Fredirect%2525252525253Dhttps%252525252525253A%252525252525252F%252525252525252Fsubdomain.example.com%252525252525252Fauth%252525252525252Fresource%252525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%252525252525253Fredirect%252525252525253Dhttps%25252525252525253A%25252525252525252F%25252525252525252Fsubdomain.example.com%25252525252525252Fauth%25252525252525252Fresource%25252525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25252525252525253Fredirect%25252525252525253Dhttps%2525252525252525253A%2525252525252525252F%2525252525252525252Fsubdomain.example.com%2525252525252525252Fapi%2525252525252525252Fv1%2525252525252525252Fauth%2525252525252525252Fnewt%2525252525252525252Fget-token": stopped after 10 redirects. Retrying in 3s...
  1. Observe that synthing is now infinitely redirecting - I'll spare you the log, it looks similar to the newt log

The infinite redirect may be my specific use case as the base domain domain.example.org is the domain where Pangolin lives. Checking the database, I see that that several entries are nulled and I have to manually fix them in the database

Image

(I anonymized the base domain in the above screenshot - so syncthing in this example has set the base domain of domain.example.org instead of the example of syncthing-host1.domain.example.org). Since there are now multiple entries in traefik with the same base domain, I assume that is why I get infinite redirects.
Given the above example from the screenshot, I need to set the subdomain for n8n, as well as the fullDomain (i.e. n8n.domain.example.org) in the database and...


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

*Originally created by @Copilot on 11/4/2025* When multiple Newt instances use identical resource names in Docker labels (e.g., `pangolin.proxy-resources.syncthing` on host1 and host2), they overwrite the same database record, nulling fields and causing service outages. ## Changes **Duplicate detection in blueprint application** - Check if resource `niceId` exists with targets/config on different `siteId` before update - Throw on cross-site name collision with actionable error message - Allow same-site updates (multiple containers sharing one resource on same host) **Error handling** - Surface site names in conflict and suggest unique naming (e.g., `syncthing-host1`) - Propagate errors to Newt instances via existing blueprint result channel **Implementation details** - `proxyResources.ts`: Query existing targets by `siteId`, validate against update source - `clientResources.ts`: Validate `existingResource.siteId !== site.siteId` before update - Use `inArray` for efficient multi-site lookups ## Example error ``` Duplicate resource name 'syncthing': already in use on site [host1]. Use unique name on site [host2]. Suggestion: 'syncthing-host2' ``` Previously this scenario corrupted the database silently. Now it fails fast with clear remediation. > [!WARNING] > > <details> > <summary>Firewall rules blocked me from connecting to one or more addresses (expand for details)</summary> > > #### I tried to connect to the following addresses, but was blocked by firewall rules: > > - `scarf.sh` > - Triggering command: `node ./report.js` (dns block) > > If you need me to access, download, or install something from one of these locations, you can either: > > - Configure [Actions setup steps](https://gh.io/copilot/actions-setup-steps) to set up my environment, which run before the firewall is enabled > - Add the appropriate URLs or hosts to the custom allowlist in this repository's [Copilot coding agent settings](https://github.com/fosrl/pangolin/settings/copilot/coding_agent) (admins only) > > </details> <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> ---- *This section details on the original issue you should resolve* <issue_title>Duplicate docker labels breaking Pangolin</issue_title> <issue_description>### Describe the Bug I'm trying out Pangolin and want to get away from the hidden database-method of configuring things, I want everything with docker labels - enter blueprints. Unfortunately when you do a configuration mistake and create duplicate names, **all** docker labels configured resources are deleted in the database, more specifically: `subDomain` is nulled and `fullDomain` is set to the base domain. ### Environment OS Type & Version: Debian & Ubuntu Pangolin Version: 1.11.0 Gerbil Version: 1.2.1 Traefik Version: v3.5.3 Newt Version: 1.5.2 ### To Reproduce 1. Have host1 & host2 2. Configure host1 as follows: ``` labels: pangolin.proxy-resources.syncthing.name: syncthing pangolin.proxy-resources.syncthing.full-domain: syncthing-host1.domain.example.org pangolin.proxy-resources.syncthing.protocol: http pangolin.proxy-resources.syncthing.auth.sso-enabled: "true" pangolin.proxy-resources.syncthing.targets[0].method: http pangolin.proxy-resources.syncthing.targets[0].hostname: syncthing pangolin.proxy-resources.syncthing.targets[0].port: 8384 ``` 3. Configure host2 as follows ``` labels: pangolin.proxy-resources.syncthing.name: syncthing pangolin.proxy-resources.syncthing.full-domain: syncthing-host2.domain.example.org pangolin.proxy-resources.syncthing.protocol: http pangolin.proxy-resources.syncthing.auth.sso-enabled: "true" pangolin.proxy-resources.syncthing.targets[0].method: http pangolin.proxy-resources.syncthing.targets[0].hostname: syncthing pangolin.proxy-resources.syncthing.targets[0].port: 8384 ``` 3. Restart newt on host1 & host2. There are no errors in either newt log and no errors in the Pangolin log 4. Everything is still working 5. Restart Pangolin on host0 - no errors in the log. 6. Observe that newt on host1&host2 are throwing errors: ``` ERROR: 2025/10/20 09:55:23 Failed to connect: failed to get token: failed to request new token: Post "https://cloud.gaida.biz/auth/resource/bdfd60bc-0193-409e-8a97-4bb0500b61ff?redirect=https%3A%2F%2Fsubdomain.example.com%2Fauth%2Fresource%2Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%3Fredirect%3Dhttps%253A%252F%252Fsubdomain.example.com%252Fauth%252Fresource%252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%253Fredirect%253Dhttps%25253A%25252F%25252Fsubdomain.example.com%25252Fauth%25252Fresource%25252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25253Fredirect%25253Dhttps%2525253A%2525252F%2525252Fsubdomain.example.com%2525252Fauth%2525252Fresource%2525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%2525253Fredirect%2525253Dhttps%252525253A%252525252F%252525252Fsubdomain.example.com%252525252Fauth%252525252Fresource%252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%252525253Fredirect%252525253Dhttps%25252525253A%25252525252F%25252525252Fsubdomain.example.com%25252525252Fauth%25252525252Fresource%25252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25252525253Fredirect%25252525253Dhttps%2525252525253A%2525252525252F%2525252525252Fsubdomain.example.com%2525252525252Fauth%2525252525252Fresource%2525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%2525252525253Fredirect%2525252525253Dhttps%252525252525253A%252525252525252F%252525252525252Fsubdomain.example.com%252525252525252Fauth%252525252525252Fresource%252525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%252525252525253Fredirect%252525252525253Dhttps%25252525252525253A%25252525252525252F%25252525252525252Fsubdomain.example.com%25252525252525252Fauth%25252525252525252Fresource%25252525252525252Fbdfd60bc-0193-409e-8a97-4bb0500b61ff%25252525252525253Fredirect%25252525252525253Dhttps%2525252525252525253A%2525252525252525252F%2525252525252525252Fsubdomain.example.com%2525252525252525252Fapi%2525252525252525252Fv1%2525252525252525252Fauth%2525252525252525252Fnewt%2525252525252525252Fget-token": stopped after 10 redirects. Retrying in 3s... ``` 8. Observe that synthing is now infinitely redirecting - I'll spare you the log, it looks similar to the newt log The infinite redirect may be my specific use case as the base domain `domain.example.org` is the domain where Pangolin lives. Checking the database, I see that that several entries are nulled and I have to manually fix them in the database <img width="999" height="214" alt="Image" src="https://github.com/user-attachments/assets/9bdd09d0-6943-4439-88f6-6b982f6908a9" /> (I anonymized the base domain in the above screenshot - so syncthing in this example has set the base domain of `domain.example.org` instead of the example of `syncthing-host1.domain.example.org`). Since there are now multiple entries in traefik with the same base domain, I assume that is why I get infinite redirects. Given the above example from the screenshot, I need to set the subdomain for n8n, as well as the `fullDomain` (i.e. `n8n.domain.example.org`) in the database and... </details> - Fixes fosrl/pangolin#1709 <!-- START COPILOT CODING AGENT TIPS --> --- 💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more [Copilot coding agent tips](https://gh.io/copilot-coding-agent-tips) in the docs.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github/pangolin#675