feat(core): Use hostname in host ID for Docker (#16544)

This commit is contained in:
Iván Ovejero
2025-07-30 13:34:35 +02:00
committed by GitHub
parent a46fa6072e
commit 308a6f65dd
2 changed files with 15 additions and 19 deletions

View File

@@ -9,7 +9,6 @@ import { WorkerMissingEncryptionKey } from '../worker-missing-encryption-key.err
describe('InstanceSettings', () => {
const userFolder = '/test';
const settingsFile = `${userFolder}/.n8n/config`;
const mockFs = mock(fs);
const logger = mock<Logger>();
@@ -202,25 +201,16 @@ describe('InstanceSettings', () => {
const settings = createInstanceSettings();
const [instanceType, nanoid] = settings.hostId.split('-');
const [instanceType, hostId] = settings.hostId.split('-');
expect(instanceType).toEqual('main');
expect(nanoid).toHaveLength(16); // e.g. sDX6ZPc0bozv66zM
expect(hostId.length).toBeGreaterThan(0); // hostname or nanoID
});
});
describe('isDocker', () => {
let settings: InstanceSettings;
beforeEach(() => {
mockFs.existsSync.calledWith(settingsFile).mockReturnValue(true);
mockFs.readFileSync
.calledWith(settingsFile)
.mockReturnValue(JSON.stringify({ encryptionKey: 'test_key' }));
settings = createInstanceSettings();
});
it('should return true if /.dockerenv exists', () => {
mockFs.existsSync.mockImplementation((path) => path === '/.dockerenv');
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(true);
expect(mockFs.existsSync).toHaveBeenCalledWith('/.dockerenv');
expect(mockFs.readFileSync).not.toHaveBeenCalledWith('/proc/self/cgroup', 'utf8');
@@ -228,6 +218,7 @@ describe('InstanceSettings', () => {
it('should return true if /run/.containerenv exists', () => {
mockFs.existsSync.mockImplementation((path) => path === '/run/.containerenv');
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(true);
expect(mockFs.existsSync).toHaveBeenCalledWith('/run/.containerenv');
expect(mockFs.readFileSync).not.toHaveBeenCalledWith('/proc/self/cgroup', 'utf8');
@@ -239,6 +230,7 @@ describe('InstanceSettings', () => {
mockFs.existsSync.mockReturnValueOnce(false);
mockFs.readFileSync.calledWith('/proc/self/cgroup', 'utf8').mockReturnValueOnce(str);
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(true);
expect(mockFs.existsSync).toHaveBeenCalledWith('/.dockerenv');
expect(mockFs.readFileSync).toHaveBeenCalledWith('/proc/self/cgroup', 'utf8');
@@ -252,6 +244,7 @@ describe('InstanceSettings', () => {
mockFs.readFileSync.calledWith('/proc/self/cgroup', 'utf8').mockReturnValueOnce('');
mockFs.readFileSync.calledWith('/proc/self/mountinfo', 'utf8').mockReturnValueOnce(str);
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(true);
expect(mockFs.existsSync).toHaveBeenCalledWith('/.dockerenv');
expect(mockFs.readFileSync).toHaveBeenCalledWith('/proc/self/cgroup', 'utf8');
@@ -263,6 +256,7 @@ describe('InstanceSettings', () => {
mockFs.existsSync.calledWith('/.dockerenv').mockReturnValueOnce(false);
mockFs.readFileSync.calledWith('/proc/self/cgroup', 'utf8').mockReturnValueOnce('');
mockFs.readFileSync.calledWith('/proc/self/mountinfo', 'utf8').mockReturnValueOnce('');
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(false);
});
@@ -272,12 +266,14 @@ describe('InstanceSettings', () => {
throw new Error('File not found');
});
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(false);
});
it('should cache the result of isDocker check', () => {
mockFs.existsSync.calledWith('/.dockerenv').mockReturnValueOnce(true);
const settings = createInstanceSettings();
expect(settings.isDocker).toBe(true);
mockFs.existsSync.mockClear();

View File

@@ -7,6 +7,7 @@ import { createHash, randomBytes } from 'crypto';
import { ApplicationError, jsonParse, ALPHABET, toResult } from 'n8n-workflow';
import { customAlphabet } from 'nanoid';
import { chmodSync, existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
import os from 'node:os';
import path from 'path';
import { WorkerMissingEncryptionKey } from './worker-missing-encryption-key.error';
@@ -60,7 +61,7 @@ export class InstanceSettings {
const command = process.argv[2] as InstanceType;
this.instanceType = ['webhook', 'worker'].includes(command) ? command : 'main';
this.hostId = `${this.instanceType}-${nanoid()}`;
this.hostId = `${this.instanceType}-${this.isDocker ? os.hostname() : nanoid()}`;
this.settings = this.loadOrCreate();
this.instanceId = this.generateInstanceId();
}
@@ -76,12 +77,11 @@ export class InstanceSettings {
instanceRole: InstanceRole = 'unset';
/**
* Transient ID of this n8n instance, for scaling mode.
* Reset on restart. Do not confuse with `instanceId`.
* ID of this n8n instance. Hostname-based when in Docker, or nanoID-based
* otherwise (resets on restart). Do not confuse with `instanceId`.
*
* @example 'main-bnxa1riryKUNHtln'
* @example 'worker-nDJR0FnSd2Vf6DB5'
* @example 'webhook-jxQ7AO8IzxEtfW1F'
* @example 'main-bnxa1riryKUNHtln' (local)
* @example 'main-6bf523178bc6' (Docker)
*/
readonly hostId: string;