Skip to content

Commit d3536bb

Browse files
author
Pascal Klesse
committed
refactor: improve Traefik v3 migration with async processing
- Run container migration in background to prevent HTTP timeouts - Add detailed progress logging with container-by-container status - Load containers with full relations before migration - Add 2s delay between migrations to avoid overwhelming Docker - Create dynamic directory for Traefik file provider - Move deploy.party update before migration (required for endpoint) - Improve user feedback with background process monitoring tips - Export env vars when deploying Traefik stack - Replace read -sp with stty for /bin/sh compatibility - Update documentation to reflect async migration behavior
1 parent 1c8ab6a commit d3536bb

File tree

3 files changed

+133
-57
lines changed

3 files changed

+133
-57
lines changed

MIGRATION.md

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@ After upgrading Traefik to v3, all deployed containers must be migrated to use t
3535
### Automatic Migration (Recommended)
3636

3737
The `upgrade-traefik-v3.sh` script will prompt you for a deploy.party API token and automatically:
38-
1. Find all deployed containers
39-
2. Stop each container
40-
3. Regenerate docker-compose.yml with updated middleware syntax
41-
4. Redeploy the container
42-
5. Continue with the next container (rolling update)
38+
1. Start the migration in the background
39+
2. Each container is processed sequentially:
40+
- Stop container
41+
- Regenerate docker-compose.yml with updated middleware syntax
42+
- Redeploy container
43+
3. Migration runs asynchronously to avoid HTTP timeouts
44+
4. Progress can be monitored via API logs
4345

4446
### Manual Migration via REST API
4547

@@ -145,10 +147,10 @@ The script will:
145147
- ✅ Stop Traefik v2 safely
146148
- ✅ Download the correct v3 configuration
147149
- ✅ Start Traefik v3
150+
- ✅ Update deploy.party to latest version (required for migration endpoint)
148151
- ✅ Auto-detect API URL from APP_URL environment variable
149152
- ✅ Prompt for API token or accept via parameter
150153
- ✅ Migrate all containers with rolling update
151-
- ✅ Update deploy.party to latest version
152154
- ✅ Verify the upgrade was successful
153155
- ✅ Provide rollback instructions if anything goes wrong
154156

projects/api/src/server/extern.controller.ts

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class ExternController {
5050
return 'Invalid API Token';
5151
}
5252

53-
const containers = await this.containerService.findContainersForProject(projectId);
53+
const containers = await this.containerService.findContainersForProject(projectId, { force: true });
5454

5555
// loop through containers
5656
for (const container of containers) {
@@ -104,7 +104,7 @@ export class ExternController {
104104
return 'Invalid API Token';
105105
}
106106

107-
const containers = await this.containerService.findContainersForProject(projectId);
107+
const containers = await this.containerService.findContainersForProject(projectId, { force: true });
108108
const dbContainer = containers.find((container) => container.kind === ContainerKind.DATABASE);
109109
if (!dbContainer) {
110110
throw new Error('No database container found');
@@ -268,11 +268,11 @@ export class ExternController {
268268
@Post('migrate/traefik-middleware')
269269
@ApiOperation({
270270
summary: 'Migrate Traefik middleware configuration',
271-
description: 'Updates all deployed containers to use Traefik v3/v4 middleware syntax with @swarm provider suffix. Performs rolling update to minimize downtime.',
271+
description: 'Updates all deployed containers to use Traefik v3/v4 middleware syntax with @swarm provider suffix. Performs rolling update to minimize downtime. This operation runs in the background and returns immediately.',
272272
})
273273
@ApiResponse({
274274
status: 200,
275-
description: 'Migration completed with details about success and failures',
275+
description: 'Migration started successfully. Check server logs for progress.',
276276
})
277277
async migrateTraefikMiddleware(@Headers('dp-api-token') apiToken: string) {
278278
if (!apiToken) {
@@ -284,29 +284,66 @@ export class ExternController {
284284
return 'Invalid API Token';
285285
}
286286

287-
const allDeployed = await this.containerService.findForce({ filterQuery: { status: ContainerStatus.DEPLOYED } });
287+
const allDeployed = await this.containerService.findForce({
288+
filterQuery: { status: ContainerStatus.DEPLOYED }
289+
});
288290

289291
// Filter out database containers (they don't use Traefik)
290-
const deployedContainers = allDeployed.filter(
291-
(c: Container) => c.kind !== ContainerKind.DATABASE
292-
);
292+
const deployedContainerIds = allDeployed
293+
.filter((c: Container) => c.kind !== ContainerKind.DATABASE)
294+
.map(c => c.id);
295+
296+
const totalContainers = deployedContainerIds.length;
297+
298+
console.debug(`[Traefik Migration] Starting background migration for ${totalContainers} containers...`);
299+
300+
// Run migration in background (don't await)
301+
this.runMigrationInBackground(deployedContainerIds).catch(error => {
302+
console.error('[Traefik Migration] Unexpected error in background migration:', error);
303+
});
304+
305+
// Return immediately
306+
return {
307+
success: true,
308+
message: 'Migration started in background',
309+
total: totalContainers,
310+
note: 'Check server logs for progress. Migration may take several minutes.',
311+
};
312+
}
293313

314+
private async runMigrationInBackground(containerIds: string[]) {
294315
const result = {
295316
success: true,
296-
total: deployedContainers.length,
317+
total: containerIds.length,
297318
migrated: 0,
298319
failed: 0,
299320
details: [],
300321
};
301322

302-
console.debug(`Starting Traefik middleware migration for ${deployedContainers.length} containers...`);
323+
for (let i = 0; i < containerIds.length; i++) {
324+
const containerId = containerIds[i];
325+
const progress = `[${i + 1}/${containerIds.length}]`;
326+
let container: Container;
303327

304-
for (const container of deployedContainers) {
305328
try {
306-
console.debug(`Migrating container ${container.id} (${container.name})...`);
329+
// Load container with all relations (registry, source, etc.)
330+
console.debug(`${progress} Loading container ${containerId}...`);
331+
container = await this.containerService.get(containerId, {
332+
populate: [{ path: 'registry' }, { path: 'source' }, { path: 'project' }]
333+
});
334+
335+
console.debug(`${progress} Migrating container: ${container.name} (${container.id})`);
307336

337+
// Stop container
338+
console.debug(`${progress} - Stopping container...`);
308339
await this.dockerService.stop(container);
340+
341+
// Regenerate docker-compose with updated middleware syntax
342+
console.debug(`${progress} - Regenerating docker-compose.yml...`);
309343
await this.dockerService.createDockerComposeFile(container);
344+
345+
// Deploy container
346+
console.debug(`${progress} - Deploying container...`);
310347
await this.dockerService.deploy(container);
311348

312349
result.migrated++;
@@ -316,23 +353,43 @@ export class ExternController {
316353
status: 'success',
317354
});
318355

319-
console.debug(`✓ Successfully migrated container ${container.id}`);
356+
console.debug(`${progress} ✓ Successfully migrated: ${container.name}`);
357+
358+
// Small delay between containers to avoid overwhelming Docker
359+
if (i < containerIds.length - 1) {
360+
await new Promise(resolve => setTimeout(resolve, 2000));
361+
}
320362
} catch (error) {
321363
result.failed++;
322364
result.success = false;
323365
result.details.push({
324-
containerId: container.id,
325-
name: container.name,
366+
containerId: containerId,
367+
name: container?.name || 'Unknown',
326368
status: 'failed',
327369
error: error.message,
370+
stack: error.stack,
328371
});
329372

330-
console.error(`✗ Failed to migrate container ${container.id}:`, error.message);
373+
console.error(`${progress} ✗ Failed to migrate: ${container?.name || containerId}`);
374+
console.error(`${progress} Error:`, error.message);
375+
console.error(`${progress} Stack:`, error.stack);
376+
377+
// Continue with next container even if this one failed
331378
}
332379
}
333380

334-
console.debug(`Migration completed: ${result.migrated} succeeded, ${result.failed} failed`);
335-
336-
return result;
381+
console.debug(`[Traefik Migration] ========================================`);
382+
console.debug(`[Traefik Migration] MIGRATION COMPLETED`);
383+
console.debug(`[Traefik Migration] Total: ${result.total}`);
384+
console.debug(`[Traefik Migration] Migrated: ${result.migrated}`);
385+
console.debug(`[Traefik Migration] Failed: ${result.failed}`);
386+
console.debug(`[Traefik Migration] ========================================`);
387+
388+
if (result.failed > 0) {
389+
console.error('[Traefik Migration] Failed containers:');
390+
result.details
391+
.filter(d => d.status === 'failed')
392+
.forEach(d => console.error(` - ${d.name}: ${d.error}`));
393+
}
337394
}
338395
}

upgrade-traefik-v3.sh

Lines changed: 49 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,17 @@ else
255255
exit 1
256256
fi
257257

258+
# Create dynamic directory for Traefik file provider if it doesn't exist
259+
echo "--------------------------------------------------------------------------------"
260+
echo "Checking dynamic configuration directory..."
261+
if [ ! -d "dynamic" ]; then
262+
echo "Creating dynamic directory..."
263+
mkdir -p dynamic
264+
echo -e "${GREEN}Dynamic directory created${NC}"
265+
else
266+
echo "Dynamic directory already exists."
267+
fi
268+
258269
# Ensure traefik-public network exists
259270
echo "--------------------------------------------------------------------------------"
260271
echo "Checking traefik-public network..."
@@ -268,7 +279,7 @@ fi
268279
# Start Traefik v3
269280
echo "--------------------------------------------------------------------------------"
270281
echo "Starting Traefik v3..."
271-
docker stack deploy -c docker-compose.traefik.yml traefik
282+
export $(cat /data/.env | grep -v '#' | awk '/=/ {print $1}') && docker stack deploy -c docker-compose.traefik.yml traefik
272283

273284
# Wait for Traefik to start
274285
echo "--------------------------------------------------------------------------------"
@@ -309,7 +320,18 @@ while true; do
309320
echo " docker logs \$(docker ps -q -f name=traefik)"
310321
echo ""
311322

323+
# Update deploy.party first (required for migration endpoint)
324+
echo "--------------------------------------------------------------------------------"
325+
echo "Updating deploy.party"
326+
echo "--------------------------------------------------------------------------------"
327+
echo ""
328+
echo "Updating deploy.party to latest version (required for migration endpoint)..."
329+
echo ""
330+
331+
update_deploy_party
332+
312333
# Container Migration for Traefik v3 Middleware Syntax
334+
echo ""
313335
echo "--------------------------------------------------------------------------------"
314336
echo "Container Migration for Traefik v3"
315337
echo "--------------------------------------------------------------------------------"
@@ -324,11 +346,6 @@ while true; do
324346
echo ""
325347
echo "To migrate containers later, run:"
326348
echo " curl -X POST -H \"dp-api-token: YOUR_API_TOKEN\" https://YOUR_API_URL/extern/migrate/traefik-middleware"
327-
echo ""
328-
329-
# Update deploy.party before exit
330-
update_deploy_party
331-
332349
echo ""
333350
echo -e "${GREEN}Traefik v3 upgrade completed.${NC}"
334351
exit 0
@@ -350,11 +367,6 @@ while true; do
350367
echo ""
351368
echo "To migrate containers later, run:"
352369
echo " curl -X POST -H \"dp-api-token: YOUR_API_TOKEN\" https://YOUR_API_URL/extern/migrate/traefik-middleware"
353-
echo ""
354-
355-
# Update deploy.party before exit
356-
update_deploy_party
357-
358370
echo ""
359371
echo -e "${GREEN}Traefik v3 upgrade completed.${NC}"
360372
exit 0
@@ -368,19 +380,18 @@ while true; do
368380
echo "API token required for automatic container migration."
369381
echo "Get your token from: Settings → API Keys (starts with dp-)"
370382
echo ""
371-
read -sp "Enter your API token (or press Enter to skip): " API_TOKEN
383+
# Use stty for compatibility with /bin/sh
384+
printf "Enter your API token (or press Enter to skip): "
385+
stty -echo
386+
read API_TOKEN
387+
stty echo
372388
echo ""
373389

374390
if [ -z "$API_TOKEN" ]; then
375391
echo -e "${YELLOW}No API token provided. Skipping automatic migration.${NC}"
376392
echo ""
377393
echo "To migrate containers later, run:"
378394
echo " curl -X POST -H \"dp-api-token: YOUR_API_TOKEN\" $API_URL/extern/migrate/traefik-middleware"
379-
echo ""
380-
381-
# Update deploy.party before exit
382-
update_deploy_party
383-
384395
echo ""
385396
echo -e "${GREEN}Traefik v3 upgrade completed.${NC}"
386397
exit 0
@@ -399,42 +410,48 @@ while true; do
399410
"$API_URL/extern/migrate/traefik-middleware")
400411

401412
if echo "$MIGRATION_RESULT" | grep -q '"success":true'; then
402-
echo -e "${GREEN}Container migration completed successfully!${NC}"
413+
TOTAL_CONTAINERS=$(echo "$MIGRATION_RESULT" | grep -o '"total":[0-9]*' | grep -o '[0-9]*')
414+
echo -e "${GREEN}Container migration started successfully!${NC}"
415+
echo ""
416+
echo "Migration running in background for $TOTAL_CONTAINERS containers."
417+
echo ""
418+
echo -e "${YELLOW}NOTE: The migration is running in the background and may take several minutes.${NC}"
419+
echo "Each container will be stopped, regenerated, and redeployed one by one."
403420
echo ""
404-
echo "Migration details:"
405-
echo "$MIGRATION_RESULT" | grep -o '"migrated":[0-9]*' | sed 's/"migrated":/ Migrated: /'
406-
echo "$MIGRATION_RESULT" | grep -o '"failed":[0-9]*' | sed 's/"failed":/ Failed: /'
421+
echo "To monitor progress, check the API logs:"
422+
echo " docker service logs -f deploy-party_api | grep 'Traefik Migration'"
423+
echo ""
424+
echo "Or check running containers:"
425+
echo " watch -n 2 'docker ps --format \"table {{.Names}}\t{{.Status}}\" | grep -v traefik'"
407426
elif echo "$MIGRATION_RESULT" | grep -q "Invalid API Token"; then
408427
echo -e "${RED}Error: Invalid API token provided.${NC}"
409428
echo ""
410429
echo "To migrate containers manually with correct token, run:"
411430
echo " curl -X POST -H \"dp-api-token: YOUR_API_TOKEN\" $API_URL/extern/migrate/traefik-middleware"
412431
else
413-
echo -e "${YELLOW}Warning: Container migration encountered issues.${NC}"
432+
echo -e "${YELLOW}Warning: Could not start container migration.${NC}"
414433
echo "Response: $MIGRATION_RESULT"
415434
echo ""
416435
echo "To retry migration manually, run:"
417436
echo " curl -X POST -H \"dp-api-token: YOUR_API_TOKEN\" $API_URL/extern/migrate/traefik-middleware"
418437
fi
419438

420-
echo ""
421-
422-
# deploy.party Update Section
423-
update_deploy_party
424-
425439
echo ""
426440
echo "================================================================================"
427441
echo -e "${GREEN}Upgrade completed successfully!${NC}"
428442
echo "================================================================================"
429443
echo ""
430-
echo "✅ Traefik upgraded from v2 to v3"
431-
if [ "$SKIP_MIGRATION" != true ]; then
432-
MIGRATED_COUNT=$(echo "$MIGRATION_RESULT" | grep -o '"migrated":[0-9]*' | grep -o '[0-9]*')
433-
echo "✅ Container middleware migrated ($MIGRATED_COUNT containers)"
434-
fi
435444
if [ "$SKIP_DEPLOY_PARTY_UPDATE" != true ]; then
436445
echo "✅ deploy.party updated to latest version"
437446
fi
447+
echo "✅ Traefik upgraded from v2 to v3"
448+
if [ "$SKIP_MIGRATION" != true ]; then
449+
if [ -n "$TOTAL_CONTAINERS" ]; then
450+
echo "✅ Container migration started for $TOTAL_CONTAINERS containers (running in background)"
451+
else
452+
echo "⚠️ Container migration status unknown - check logs"
453+
fi
454+
fi
438455
echo ""
439456
echo "Dashboard: lb.$APP_URL"
440457
echo "deploy.party: $APP_URL"

0 commit comments

Comments
 (0)