Skip to content

Commit 47b2384

Browse files
gfraiteurclaude
andcommitted
Add Docker cache invalidation and improve container file organization
Implement -Update switch for DockerBuild.ps1 (Claude mode only) that updates a timestamp file to invalidate Docker layer cache, forcing plugin updates. Reorganize docker-context to use .g/ subdirectory for generated files (env.g.json, Init.g.ps1, update.timestamp). Move version counter files from %APPDATA% (Roaming) to %LOCALAPPDATA% (Local) for proper sharing between host and container. Fix container user profile to consistently use ContainerAdministrator instead of ContainerUser. Copy docker context files to c:\docker-context\ in container instead of c:\ root for cleaner organization. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent fff0c10 commit 47b2384

File tree

10 files changed

+442
-261
lines changed

10 files changed

+442
-261
lines changed

.gitignore

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
1-
obj
2-
bin
3-
artifacts
4-
.vs
5-
.suo
6-
eng/tools
7-
*.Import.props
8-
*.g.props
9-
*.user
10-
.teamcity/.idea
11-
.teamcity/target
12-
/.config/dotnet-tools.json
13-
/.tools
14-
nuget.config
15-
global.json
16-
*.g.json
17-
*.g.ps1
18-
eng/docker-context/claude.json
19-
scripts/build-agents/last-execution.txt
20-
*.log
21-
copilot.data.*
22-
eng/docker-context/.credentials.json
1+
obj
2+
bin
3+
artifacts
4+
.vs
5+
.suo
6+
eng/tools
7+
*.Import.props
8+
*.g.props
9+
*.user
10+
.teamcity/.idea
11+
.teamcity/target
12+
/.config/dotnet-tools.json
13+
/.tools
14+
nuget.config
15+
global.json
16+
*.g.json
17+
*.g.ps1
18+
eng/docker-context/claude.json
19+
eng/docker-context/.g/
20+
scripts/build-agents/last-execution.txt
21+
*.log
22+
copilot.data.*
23+
eng/docker-context/.credentials.json

DockerBuild.ps1

Lines changed: 81 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ param(
1111
[switch]$KeepEnv, # Does not override the env.g.json file.
1212
[switch]$Claude, # Run Claude CLI instead of Build.ps1. Use -Claude for interactive, -Claude "prompt" for non-interactive.
1313
[switch]$NoMcp, # Do not start the MCP approval server (for -Claude mode).
14+
[switch]$Update, # Update timestamp to invalidate Docker cache and force Claude/plugin updates (Claude mode only).
1415
[string]$ImageName, # Image name (defaults to a name based on the directory).
1516
[string]$BuildAgentPath = $(if ($env:TEAMCITY_JRE) { Split-Path $env:TEAMCITY_JRE -Parent } else { 'C:\BuildAgent' }),
1617
[switch]$LoadEnvFromKeyVault, # Forces loading environment variables form the key vault.
@@ -94,12 +95,13 @@ function New-EnvJson
9495
}
9596

9697
# Convert to JSON and save
97-
$jsonPath = Join-Path $dockerContextDirectory "env.g.json"
98+
$gDirectory = Join-Path $dockerContextDirectory ".g"
99+
$jsonPath = Join-Path $gDirectory "env.g.json"
98100

99101
# Ensure the directory exists
100-
if (-not (Test-Path $dockerContextDirectory))
102+
if (-not (Test-Path $gDirectory))
101103
{
102-
New-Item -ItemType Directory -Path $dockerContextDirectory -Force | Out-Null
104+
New-Item -ItemType Directory -Path $gDirectory -Force | Out-Null
103105
}
104106

105107
# Write a test JSON file with GUID first
@@ -178,12 +180,13 @@ function New-ClaudeEnvJson
178180
$claudeEnv["NUGET_PACKAGES"] = $nugetPackages
179181

180182
# Convert to JSON and save
181-
$jsonPath = Join-Path $dockerContextDirectory "env.g.json"
183+
$gDirectory = Join-Path $dockerContextDirectory ".g"
184+
$jsonPath = Join-Path $gDirectory "env.g.json"
182185

183186
# Ensure the directory exists
184-
if (-not (Test-Path $dockerContextDirectory))
187+
if (-not (Test-Path $gDirectory))
185188
{
186-
New-Item -ItemType Directory -Path $dockerContextDirectory -Force | Out-Null
189+
New-Item -ItemType Directory -Path $gDirectory -Force | Out-Null
187190
}
188191

189192
# Write a test JSON file with GUID first
@@ -325,6 +328,32 @@ function Copy-McpServerToTemp
325328
}
326329
}
327330

331+
function Get-TimestampFile
332+
{
333+
param(
334+
[switch]$Update
335+
)
336+
337+
$timestampDir = Join-Path $env:LOCALAPPDATA "PostSharp.Engineering"
338+
$timestampFile = Join-Path $timestampDir "update.timestamp"
339+
340+
# Ensure directory exists
341+
if (-not (Test-Path $timestampDir))
342+
{
343+
New-Item -ItemType Directory -Path $timestampDir -Force | Out-Null
344+
}
345+
346+
# Create file if it doesn't exist OR update if -Update specified
347+
if (-not (Test-Path $timestampFile) -or $Update)
348+
{
349+
$timestamp = [DateTime]::UtcNow.ToString("o") # ISO 8601 format
350+
Set-Content -Path $timestampFile -Value $timestamp -NoNewline -Force
351+
Write-Host "Timestamp file updated: $timestamp" -ForegroundColor Cyan
352+
}
353+
354+
return $timestampFile
355+
}
356+
328357
if ($env:RUNNING_IN_DOCKER)
329358
{
330359
Write-Error "Already running in Docker."
@@ -400,6 +429,12 @@ if (-not $KeepEnv)
400429
{
401430
# Use Claude-specific environment variables (filtered and renamed)
402431
New-ClaudeEnvJson
432+
433+
# Get/update timestamp file for cache invalidation (only if building image)
434+
if (-not $NoBuildImage)
435+
{
436+
$timestampFile = Get-TimestampFile -Update:$Update
437+
}
403438
}
404439
else
405440
{
@@ -444,6 +479,9 @@ if (-not (Test-Path $dockerContextDirectory))
444479
}
445480

446481

482+
# Container user profile (matches actual Windows user in container)
483+
$containerUserProfile = "C:\Users\ContainerAdministrator"
484+
447485
# Prepare volume mappings (stored as mapping strings, "-v" flags added later)
448486
$VolumeMappings = @("${SourceDirName}:${SourceDirName}")
449487
$MountPoints = @($SourceDirName)
@@ -480,6 +518,17 @@ if (-not $NoNuGetCache)
480518
$MountPoints += $nugetCacheDir
481519
}
482520

521+
# Mount PostSharp.Engineering data directory (for version counters)
522+
$hostEngineeringDataDir = Join-Path $env:LOCALAPPDATA "PostSharp.Engineering"
523+
if (-not (Test-Path $hostEngineeringDataDir))
524+
{
525+
New-Item -ItemType Directory -Force -Path $hostEngineeringDataDir | Out-Null
526+
}
527+
528+
$containerEngineeringDataDir = Join-Path $containerUserProfile "AppData\Local\PostSharp.Engineering"
529+
$VolumeMappings += "${hostEngineeringDataDir}:${containerEngineeringDataDir}"
530+
$MountPoints += $containerEngineeringDataDir
531+
483532
# Mount VS Remote Debugger
484533
if ($StartVsmon)
485534
{
@@ -660,7 +709,12 @@ if ($driveLetters.Count -gt 0)
660709
}
661710

662711
# Create Init.g.ps1 with git configuration (safe.directory and user identity)
663-
$initScript = Join-Path $dockerContextDirectory "Init.g.ps1"
712+
$gDirectory = Join-Path $dockerContextDirectory ".g"
713+
if (-not (Test-Path $gDirectory))
714+
{
715+
New-Item -ItemType Directory -Path $gDirectory -Force | Out-Null
716+
}
717+
$initScript = Join-Path $gDirectory "Init.g.ps1"
664718
$initScriptContent = @"
665719
# Auto-generated initialization script for container startup
666720
@@ -692,6 +746,19 @@ foreach (`$dir in `$gitDirectories) {
692746
"@
693747
$initScriptContent | Set-Content -Path $initScript -Encoding UTF8
694748

749+
# Copy timestamp file to docker context (for Claude mode cache invalidation)
750+
if ($Claude -and $timestampFile)
751+
{
752+
$gDirectory = Join-Path $dockerContextDirectory ".g"
753+
if (-not (Test-Path $gDirectory))
754+
{
755+
New-Item -ItemType Directory -Path $gDirectory -Force | Out-Null
756+
}
757+
$timestampDestination = Join-Path $gDirectory "update.timestamp"
758+
Copy-Item -Path $timestampFile -Destination $timestampDestination -Force
759+
Write-Host "Copied timestamp file to docker context" -ForegroundColor Cyan
760+
}
761+
695762
$mountPointsAsString = $MountPoints -Join ";"
696763
$gitDirectoriesAsString = $GitDirectories -Join ";"
697764

@@ -878,7 +945,6 @@ if (-not $BuildImage)
878945

879946
# Container will have its own Claude profile (no mount, no copy from host)
880947
$hostUserProfile = $env:USERPROFILE
881-
$containerUserProfile = "C:\Users\ContainerUser"
882948

883949
# Convert volume mappings to docker args format (interleave "-v" flags)
884950
$volumeArgs = @()
@@ -930,7 +996,7 @@ if (-not $BuildImage)
930996
{
931997
""
932998
}
933-
$inlineScript = "${substCommandsInline}& c:\Init.g.ps1; cd '$SourceDirName'; & .\eng\RunClaude.ps1 -Prompt `"$ClaudePrompt`"$mcpArg"
999+
$inlineScript = "${substCommandsInline}& c:\docker-context\Init.g.ps1; cd '$SourceDirName'; & .\eng\RunClaude.ps1 -Prompt `"$ClaudePrompt`"$mcpArg"
9341000
}
9351001
else
9361002
{
@@ -944,17 +1010,14 @@ if (-not $BuildImage)
9441010
{
9451011
""
9461012
}
947-
$inlineScript = "${substCommandsInline}& c:\Init.g.ps1; cd '$SourceDirName'; & .\eng\RunClaude.ps1$mcpArg"
1013+
$inlineScript = "${substCommandsInline}& c:\docker-context\Init.g.ps1; cd '$SourceDirName'; & .\eng\RunClaude.ps1$mcpArg"
9481014
}
9491015

9501016
$dockerArgsAsString = $dockerArgs -join " "
9511017
$pwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe'
9521018

953-
# Set HOME/USERPROFILE so Claude finds its config in the mounted location
954-
$envArgs = @(
955-
"-e", "HOME=$containerUserProfile",
956-
"-e", "USERPROFILE=$containerUserProfile"
957-
)
1019+
# Environment variables to pass to container
1020+
$envArgs = @()
9581021

9591022
# Pass MCP secret to container if MCP server is running
9601023
if ($mcpSecret)
@@ -965,7 +1028,8 @@ if (-not $BuildImage)
9651028
try
9661029
{
9671030
# Start new container with docker run
968-
Write-Host "Executing: docker run --rm --memory=$Memory --cpus=$Cpus --isolation=$Isolation $dockerArgsAsString $VolumeMappingsAsString -e HOME=$containerUserProfile -e USERPROFILE=$containerUserProfile -w $ContainerSourceDir $ImageTag `"$pwshPath`" -Command `"$inlineScript`"" -ForegroundColor Cyan
1031+
$envArgsAsString = ($envArgs -join " ")
1032+
Write-Host "Executing: docker run --rm --memory=$Memory --cpus=$Cpus --isolation=$Isolation $dockerArgsAsString $VolumeMappingsAsString $envArgsAsString -w $ContainerSourceDir $ImageTag `"$pwshPath`" -Command `"$inlineScript`"" -ForegroundColor Cyan
9691033
docker run --rm --memory=$Memory --cpus=$Cpus --isolation=$Isolation $dockerArgs @volumeArgs @envArgs -w $ContainerSourceDir $ImageTag $pwshPath -Command $inlineScript
9701034
$dockerExitCode = $LASTEXITCODE
9711035
}
@@ -1073,7 +1137,7 @@ if (-not $BuildImage)
10731137
$dockerArgsAsString = $dockerArgs -join " "
10741138

10751139
# Build inline script: subst drives, run init, cd to source, run build
1076-
$inlineScript = "${substCommandsInline}& c:\Init.g.ps1; cd '$SourceDirName'; & .\$Script $buildArgsString; $pwshExitCommand"
1140+
$inlineScript = "${substCommandsInline}& c:\docker-context\Init.g.ps1; cd '$SourceDirName'; & .\$Script $buildArgsString; $pwshExitCommand"
10771141

10781142
$pwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe'
10791143

0 commit comments

Comments
 (0)