mirror of
				https://github.com/factoriotools/factorio-docker.git
				synced 2025-10-31 17:08:08 +01:00 
			
		
		
		
	* feat: Add rootless Docker support Implements #547 - Add support for rootless Docker images to avoid permission issues. Key changes: - Add Dockerfile.rootless that runs as UID 1000 by default - Create simplified entrypoint script without chown operations - Add build-rootless.py to build rootless variants with -rootless suffix - Document rootless usage in README-ROOTLESS.md - Update main README with rootless section The rootless images eliminate common permission problems by: - Running as non-root from the start (USER 1000:1000) - Avoiding recursive chown operations that can cause race conditions - Using open permissions (777) on directories during build - Not supporting PUID/PGID environment variables This provides a cleaner solution for rootless Docker users and those experiencing permission issues with volumes. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Address linting issues in rootless Docker implementation - Add --no-install-recommends to apt-get install in Dockerfile - Consolidate consecutive RUN instructions in Dockerfile - Fix shellcheck warnings: quote variables and use -n instead of \! -z - These changes improve best practices without affecting functionality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: Add rootless image building to CI pipeline - Update docker-build.yml workflow to build rootless variants - Rootless images are built after regular images with -rootless suffix - Both use the same multi-architecture build process - Triggered automatically when buildinfo.json changes 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Unify build system for regular and rootless images - Create build-unified.py that handles both regular and rootless builds - Convert build.py and build-rootless.py to wrapper scripts for backwards compatibility - Update CI workflow to use unified build command - Add BUILD_MIGRATION.md documentation - Eliminate code duplication between build scripts - Support flexible build options: --rootless, --both, --only-stable-latest This maintains all existing functionality while providing a cleaner, more maintainable build system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * chore: Add Python cache to .gitignore and remove from repo - Add __pycache__/ and Python compiled files to .gitignore - Remove accidentally committed __pycache__ directory - Prevent future Python cache files from being tracked 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: Replace build system with unified solution - Remove old build.py and build-rootless.py wrapper scripts - Rename build-unified.py to build.py as the main build script - Delete BUILD_MIGRATION.md (no longer needed) - Update CI workflow to use new build.py syntax - Update documentation in CLAUDE.md and README-ROOTLESS.md The new build system provides all functionality in a single script: - Default: builds regular images - --rootless: builds only rootless images - --both: builds both regular and rootless images - --multiarch and --push-tags: work as before This creates a cleaner, more maintainable build system. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: Consolidate rootless documentation and mark as experimental - Remove separate README-ROOTLESS.md file - Integrate rootless documentation into main README.md - Mark rootless support as experimental - Add clear documentation about limitations and use cases - Include warning about experimental nature This consolidates all documentation in one place and makes it clear that rootless support is still experimental. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com>
		
			
				
	
	
		
			157 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			157 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python3
 | |
| 
 | |
| import os
 | |
| import json
 | |
| import subprocess
 | |
| import shutil
 | |
| import sys
 | |
| import tempfile
 | |
| import argparse
 | |
| 
 | |
| 
 | |
| PLATFORMS = [
 | |
|     "linux/arm64",
 | |
|     "linux/amd64",
 | |
| ]
 | |
| 
 | |
| 
 | |
| def create_builder(build_dir, builder_name, platform):
 | |
|     check_exists_command = ["docker", "buildx", "inspect", builder_name]
 | |
|     if subprocess.run(check_exists_command, stderr=subprocess.DEVNULL).returncode != 0:
 | |
|         create_command = ["docker", "buildx", "create", "--platform", platform, "--name", builder_name]
 | |
|         try:
 | |
|             subprocess.run(create_command, cwd=build_dir, check=True)
 | |
|         except subprocess.CalledProcessError:
 | |
|             print("Creating builder failed")
 | |
|             exit(1)
 | |
| 
 | |
| 
 | |
| def build_and_push_multiarch(build_dir, build_args, push, builder_suffix=""):
 | |
|     builder_name = f"factoriotools{builder_suffix}-multiarch"
 | |
|     platform = ",".join(PLATFORMS)
 | |
|     create_builder(build_dir, builder_name, platform)
 | |
|     build_command = ["docker", "buildx", "build", "--platform", platform, "--builder", builder_name] + build_args
 | |
|     if push:
 | |
|         build_command.append("--push")
 | |
|     try:
 | |
|         subprocess.run(build_command, cwd=build_dir, check=True)
 | |
|     except subprocess.CalledProcessError:
 | |
|         print(f"Build and push of {builder_suffix or 'regular'} image failed")
 | |
|         exit(1)
 | |
| 
 | |
| 
 | |
| def build_singlearch(build_dir, build_args, image_type="regular"):
 | |
|     build_command = ["docker", "build"] + build_args
 | |
|     try:
 | |
|         subprocess.run(build_command, cwd=build_dir, check=True)
 | |
|     except subprocess.CalledProcessError:
 | |
|         print(f"Build of {image_type} image failed")
 | |
|         exit(1)
 | |
| 
 | |
| 
 | |
| def push_singlearch(tags):
 | |
|     for tag in tags:
 | |
|         try:
 | |
|             subprocess.run(["docker", "push", f"factoriotools/factorio:{tag}"],
 | |
|                             check=True)
 | |
|         except subprocess.CalledProcessError:
 | |
|             print("Docker push failed")
 | |
|             exit(1)
 | |
| 
 | |
| 
 | |
| def build_and_push(sha256, version, tags, push, multiarch, dockerfile="Dockerfile", builder_suffix=""):
 | |
|     build_dir = tempfile.mktemp()
 | |
|     shutil.copytree("docker", build_dir)
 | |
|     build_args = ["-f", dockerfile, "--build-arg", f"VERSION={version}", "--build-arg", f"SHA256={sha256}", "."]
 | |
|     for tag in tags:
 | |
|         build_args.extend(["-t", f"factoriotools/factorio:{tag}"])
 | |
|     
 | |
|     image_type = "rootless" if "rootless" in dockerfile.lower() else "regular"
 | |
|     
 | |
|     if multiarch:
 | |
|         build_and_push_multiarch(build_dir, build_args, push, builder_suffix)
 | |
|     else:
 | |
|         build_singlearch(build_dir, build_args, image_type)
 | |
|         if push:
 | |
|             push_singlearch(tags)
 | |
| 
 | |
| 
 | |
| def login():
 | |
|     try:
 | |
|         username = os.environ["DOCKER_USERNAME"]
 | |
|         password = os.environ["DOCKER_PASSWORD"]
 | |
|         subprocess.run(["docker", "login", "-u", username, "-p", password], check=True)
 | |
|     except KeyError:
 | |
|         print("Username and password need to be given")
 | |
|         exit(1)
 | |
|     except subprocess.CalledProcessError:
 | |
|         print("Docker login failed")
 | |
|         exit(1)
 | |
| 
 | |
| 
 | |
| def generate_rootless_tags(original_tags):
 | |
|     """Generate rootless-specific tags from original tags"""
 | |
|     return [f"{tag}-rootless" for tag in original_tags]
 | |
| 
 | |
| 
 | |
| def main():
 | |
|     parser = argparse.ArgumentParser(description='Build Factorio Docker images')
 | |
|     parser.add_argument('--push-tags', action='store_true', help='Push images to Docker Hub')
 | |
|     parser.add_argument('--multiarch', action='store_true', help='Build multi-architecture images')
 | |
|     parser.add_argument('--rootless', action='store_true', help='Build only rootless images')
 | |
|     parser.add_argument('--both', action='store_true', help='Build both regular and rootless images')
 | |
|     parser.add_argument('--only-stable-latest', action='store_true', 
 | |
|                         help='Build only stable and latest versions (for rootless by default)')
 | |
|     
 | |
|     args = parser.parse_args()
 | |
|     
 | |
|     # Default behavior: build regular images unless specified otherwise
 | |
|     build_regular = not args.rootless or args.both
 | |
|     build_rootless = args.rootless or args.both
 | |
|     
 | |
|     with open(os.path.join(os.path.dirname(__file__), "buildinfo.json")) as file_handle:
 | |
|         builddata = json.load(file_handle)
 | |
| 
 | |
|     if args.push_tags:
 | |
|         login()
 | |
| 
 | |
|     # Filter versions if needed
 | |
|     versions_to_build = []
 | |
|     for version, buildinfo in sorted(builddata.items(), key=lambda item: item[0], reverse=True):
 | |
|         if args.only_stable_latest or (build_rootless and not build_regular):
 | |
|             # For rootless-only builds, default to stable/latest only
 | |
|             if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]:
 | |
|                 versions_to_build.append((version, buildinfo))
 | |
|         else:
 | |
|             versions_to_build.append((version, buildinfo))
 | |
|     
 | |
|     # Build regular images
 | |
|     if build_regular:
 | |
|         print("Building regular images...")
 | |
|         for version, buildinfo in versions_to_build:
 | |
|             sha256 = buildinfo["sha256"]
 | |
|             tags = buildinfo["tags"]
 | |
|             build_and_push(sha256, version, tags, args.push_tags, args.multiarch)
 | |
|     
 | |
|     # Build rootless images
 | |
|     if build_rootless:
 | |
|         print("Building rootless images...")
 | |
|         # For rootless, only build stable and latest unless building both
 | |
|         rootless_versions = []
 | |
|         if not build_regular or args.only_stable_latest:
 | |
|             for version, buildinfo in builddata.items():
 | |
|                 if "stable" in buildinfo["tags"] or "latest" in buildinfo["tags"]:
 | |
|                     rootless_versions.append((version, buildinfo))
 | |
|         else:
 | |
|             rootless_versions = versions_to_build
 | |
|             
 | |
|         for version, buildinfo in rootless_versions:
 | |
|             sha256 = buildinfo["sha256"]
 | |
|             original_tags = buildinfo["tags"]
 | |
|             rootless_tags = generate_rootless_tags(original_tags)
 | |
|             build_and_push(sha256, version, rootless_tags, args.push_tags, args.multiarch, 
 | |
|                          dockerfile="Dockerfile.rootless", builder_suffix="-rootless")
 | |
| 
 | |
| 
 | |
| if __name__ == '__main__':
 | |
|     main() |