@@ -362,7 +362,8 @@ public static async Task<Stream> ZipToSquashfsStream(Stream stream)
362362 string containerId = null ;
363363 try
364364 {
365- string dockerImage = await ChoosePythonBuildEnvImage ( ) ;
365+ DockerImageInfo result = await ChoosePythonBuildEnvImage ( ) ;
366+ string dockerImage = result . ImageName ;
366367 containerId = await DockerHelpers . DockerRun ( dockerImage , command : "sleep infinity" ) ;
367368
368369 await DockerHelpers . CopyToContainer ( containerId , tmpFile , $ "/file.zip") ;
@@ -494,14 +495,19 @@ private static async Task RestorePythonRequirementsDocker(string functionAppRoot
494495 var dockerSkipPullFlagSetting = Environment . GetEnvironmentVariable ( Constants . PythonDockerImageSkipPull ) ;
495496 var dockerRunSetting = Environment . GetEnvironmentVariable ( Constants . PythonDockerRunCommand ) ;
496497
498+ bool canPull = true ;
497499 string dockerImage = pythonDockerImageSetting ;
498500 if ( string . IsNullOrEmpty ( dockerImage ) )
499501 {
500- dockerImage = await ChoosePythonBuildEnvImage ( ) ;
502+ DockerImageInfo result = await ChoosePythonBuildEnvImage ( ) ;
503+ dockerImage = result . ImageName ;
504+ canPull = result . CanPull ;
501505 }
502506
503- if ( string . IsNullOrEmpty ( dockerSkipPullFlagSetting ) ||
504- ! ( dockerSkipPullFlagSetting . Equals ( "true" , StringComparison . OrdinalIgnoreCase ) || dockerSkipPullFlagSetting == "1" ) )
507+ if ( canPull &&
508+ ( string . IsNullOrEmpty ( dockerSkipPullFlagSetting ) ||
509+ ! ( dockerSkipPullFlagSetting . Equals ( "true" , StringComparison . OrdinalIgnoreCase ) ||
510+ dockerSkipPullFlagSetting == "1" ) ) )
505511 {
506512 await DockerHelpers . DockerPull ( dockerImage ) ;
507513 }
@@ -547,10 +553,30 @@ private static async Task RestorePythonRequirementsDocker(string functionAppRoot
547553 }
548554 }
549555
550- private static async Task < string > ChoosePythonBuildEnvImage ( )
556+ private static async Task < DockerImageInfo > ChoosePythonBuildEnvImage ( )
551557 {
552558 WorkerLanguageVersionInfo workerInfo = await GetEnvironmentPythonVersion ( ) ;
553- return GetBuildNativeDepsEnvironmentImage ( workerInfo ) ;
559+ var ( image , isLocal ) = await GetBuildNativeDepsEnvironmentImage ( workerInfo ) ;
560+
561+ if ( isLocal )
562+ {
563+ // Setup image tag and content
564+ string imageContent = image ;
565+ image = $ "azure-functions/python:4-python{ workerInfo . Major } .{ workerInfo . Minor } -buildenv";
566+
567+ // Prepare temporary directory for docker build context
568+ string tempDockerfileDirecotry = Path . Combine ( Path . GetTempPath ( ) , $ "{ image } -docker") ;
569+ FileSystemHelpers . EnsureDirectory ( tempDockerfileDirecotry ) ;
570+ string tempDockerfile = Path . Combine ( tempDockerfileDirecotry , "Dockerfile" ) ;
571+
572+ // Write Dockerfile content to temporary file
573+ await FileSystemHelpers . WriteAllTextToFileAsync ( tempDockerfile , imageContent ) ;
574+
575+ // Build the image
576+ await DockerHelpers . DockerBuild ( image , tempDockerfileDirecotry ) ;
577+ }
578+
579+ return new DockerImageInfo { ImageName = image , CanPull = ! isLocal } ;
554580 }
555581
556582 private static string CopyToTemp ( IEnumerable < string > files , string rootPath )
@@ -596,32 +622,35 @@ public static Task<string> GetDockerInitFileContent(WorkerLanguageVersionInfo in
596622 return StaticResources . DockerfilePython37 ;
597623 }
598624
599- private static string GetBuildNativeDepsEnvironmentImage ( WorkerLanguageVersionInfo info )
625+ // Build environment images for building native dependencies for python function apps
626+ private static async Task < ( string Image , bool IsLocal ) > GetBuildNativeDepsEnvironmentImage ( WorkerLanguageVersionInfo info )
600627 {
601628 if ( info ? . Major == 3 )
602629 {
603630 switch ( info ? . Minor )
604631 {
605632 case 6 :
606- return Constants . DockerImages . LinuxPython36ImageAmd64 ;
633+ return ( DockerImages . LinuxPython36ImageAmd64 , false ) ;
607634 case 7 :
608- return Constants . DockerImages . LinuxPython37ImageAmd64 ;
635+ return ( DockerImages . LinuxPython37ImageAmd64 , false ) ;
609636 case 8 :
610- return Constants . DockerImages . LinuxPython38ImageAmd64 ;
637+ return ( DockerImages . LinuxPython38ImageAmd64 , false ) ;
611638 case 9 :
612- return Constants . DockerImages . LinuxPython39ImageAmd64 ;
639+ return ( DockerImages . LinuxPython39ImageAmd64 , false ) ;
613640 case 10 :
614- return Constants . DockerImages . LinuxPython310ImageAmd64 ;
641+ return ( DockerImages . LinuxPython310ImageAmd64 , false ) ;
615642 case 11 :
616- return Constants . DockerImages . LinuxPython311ImageAmd64 ;
643+ return ( DockerImages . LinuxPython311ImageAmd64 , false ) ;
617644 case 12 :
618- return Constants . DockerImages . LinuxPython312ImageAmd64 ;
645+ return ( DockerImages . LinuxPython312ImageAmd64 , false ) ;
646+
647+ // From Python 3.13 onwards, we use a Dockerfile to build the image locally
619648 case 13 :
620- return Constants . DockerImages . LinuxPython313ImageAmd64 ;
649+ return ( await StaticResources . DockerfilePython313BuildEnv , true ) ;
621650 }
622651 }
623652
624- return Constants . DockerImages . LinuxPython312ImageAmd64 ;
653+ return ( DockerImages . LinuxPython312ImageAmd64 , false ) ;
625654 }
626655
627656 private static bool IsVersionSupported ( WorkerLanguageVersionInfo info )
0 commit comments