├── .gitignore ├── README.md └── export-container.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | export-docker 2 | ============= 3 | 4 | Export a Docker container to a lxc container 5 | 6 | How to use 7 | ---------- 8 | 9 | sudo ./export-container.py 10 | 11 | Example: 12 | 13 | $ sudo ./export-container.py f4e7e632964a registry 14 | Exporting Docker container to a lxc container 15 | Docker container id: f4e7e632964a 16 | New container name: registry 17 | Full container id f4e7e632964a822bbd0e633b8ef8422c341d0b43d3e72a869c1154e461cf2302 18 | Container exists [/var/lib/docker/containers/f4e7e632964a822bbd0e633b8ef8422c341d0b43d3e72a869c1154e461cf2302]? True 19 | Rootfs exists [/var/lib/docker/devicemapper/mnt/f4e7e632964a822bbd0e633b8ef8422c341d0b43d3e72a869c1154e461cf2302/rootfs]? True 20 | Init-rootfs exists [/var/lib/docker/devicemapper/mnt/f4e7e632964a822bbd0e633b8ef8422c341d0b43d3e72a869c1154e461cf2302-init/rootfs]? True 21 | Copying rootfs... 22 | Copying config files... 23 | Creating run script... 24 | 25 | It's all done! Now just type: 26 | cd registry && sudo ./run.sh 27 | 28 | Check the folder just created: 29 | 30 | $ find registry/ -maxdepth 2 31 | registry/ 32 | registry/rootfs 33 | registry/rootfs/usr 34 | registry/rootfs/home 35 | ... 36 | registry/rootfs/tmp 37 | registry/rootfs/root 38 | registry/config.lxc.template 39 | registry/run.sh 40 | 41 | * config.lxc.template: The lxc config file 42 | * run.sh: Script to run the container 43 | * rootfs/: Folder with all container files 44 | 45 | To run: 46 | 47 | $ sudo ./run.sh 48 | root@f4e7e632964a:/# 49 | 50 | Why 51 | --- 52 | 53 | * To have a lxc container independent from Docker (docker0 network interface is the only dependency) 54 | * To learn about lxc and Docker 55 | * For fun :) 56 | -------------------------------------------------------------------------------- /export-container.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | import io 4 | import shutil 5 | import configparser 6 | import os 7 | import subprocess 8 | import argparse 9 | from subprocess import call 10 | 11 | docker_path = "/var/lib/docker" 12 | docker_init_path = docker_path + "/init" 13 | containers_path = docker_path + "/containers" 14 | 15 | 16 | def sed(regexp, replacement, file): 17 | call(["sed", "-i", "s," + regexp + "," + replacement + ",g", file]) 18 | 19 | def copyfile(source, dest): 20 | shutil.copyfile(source, dest) 21 | 22 | 23 | class Container: 24 | 25 | def __init__(self, container_id, container_name): 26 | self.id = self.get_full_id(container_id) 27 | self.name = container_name 28 | self.short_id = self.id[:12] 29 | self.path = containers_path + "/" + self.id 30 | self.config = containers_path + "/" + self.id + "/config.lxc" 31 | self.rootfs_path = None 32 | self.init_rootfs_path = None 33 | 34 | def get_full_id(self, container_id): 35 | if len(container_id) == 64: 36 | return container_id 37 | if len(container_id) < 12: 38 | raise Exception("Please enter at least 12 container ID characters") 39 | 40 | for container_dir in os.listdir(containers_path): 41 | if container_dir.startswith(container_id): 42 | print("Full container id ", container_dir) 43 | return container_dir 44 | 45 | raise Exception("Container not found") 46 | 47 | def is_valid_container(self): 48 | self.container_name_exists() 49 | self.container_folder_exists() 50 | self.lxc_rootfs_exists() 51 | 52 | def container_name_exists(self): 53 | container_name_exists = os.path.isdir(self.name) 54 | if container_name_exists: 55 | raise Exception("Container name already exists") 56 | 57 | def container_folder_exists(self): 58 | container_exists = os.path.isdir(self.path) 59 | print("Container exists [{:s}]? {:s}".format(self.path, str(container_exists))) 60 | if not container_exists: 61 | raise Exception("Container folder not found") 62 | 63 | def lxc_rootfs_exists(self): 64 | lines = tuple(open(self.config, "r")) 65 | rootfs = None 66 | for line in lines: 67 | if line.startswith("lxc.rootfs ="): 68 | rootfs = line 69 | break 70 | 71 | if rootfs == None: 72 | raise Exception("lxc.rootfs not found") 73 | 74 | equal_index = rootfs.index("=") + 1 75 | rootfs_value = rootfs[equal_index:] 76 | self.rootfs_path = rootfs_value.strip() 77 | 78 | rootfs_exists = os.path.isdir(self.rootfs_path) 79 | print("Rootfs exists [{:s}]? {:s}".format(self.rootfs_path, str(rootfs_exists))) 80 | if not rootfs_exists: 81 | raise Exception("Rootfs folder not found") 82 | 83 | self.init_rootfs_path = self.rootfs_path.replace(self.id, self.id + "-init") 84 | init_rootfs_exists = os.path.isdir(self.init_rootfs_path) 85 | print("Init-rootfs exists [{:s}]? {:s}".format(self.init_rootfs_path, str(init_rootfs_exists))) 86 | if not init_rootfs_exists: 87 | self.init_rootfs_path = None 88 | 89 | 90 | class Exporter: 91 | 92 | def __init__(self, container): 93 | self.container = container 94 | self.allowed_mount_config = [ 95 | "proc", 96 | "sysfs", 97 | "devpts", 98 | "shm", 99 | "/etc/resolv.conf", 100 | ] 101 | 102 | def copy(self): 103 | self.create_container_folder() 104 | self.copy_init_rootfs() 105 | self.copy_config_files() 106 | self.copy_dockerinit() 107 | self.create_run_script() 108 | 109 | def create_container_folder(self): 110 | os.mkdir(self.container.name, 0o755) 111 | 112 | def copy_dockerinit(self): 113 | version = self.get_docker_version() 114 | if version == None: 115 | raise Exception("Docker version not found") 116 | 117 | copyfile(docker_init_path + "/dockerinit-" + version, self.container.name + "/rootfs/.dockerinit") 118 | 119 | def get_docker_version(self): 120 | cmd = subprocess.Popen("docker version", shell=True, stdout=subprocess.PIPE) 121 | version = None 122 | for line in cmd.stdout: 123 | line_str = str(line, encoding="utf8") 124 | if "Client version:" in line_str: 125 | return line_str[16:len(line_str) - 1] 126 | return None 127 | 128 | def copy_init_rootfs(self): 129 | print("Copying rootfs...") 130 | # shutil.copytree(self.container.init_rootfs_path, self.container.name + "/rootfs") 131 | source = self.get_rootfs_path() 132 | call(["cp", "-rp", source, self.container.name + "/rootfs"]) 133 | 134 | def get_rootfs_path(self): 135 | if self.container.init_rootfs_path != None: 136 | source = self.container.init_rootfs_path 137 | return self.container.rootfs_path 138 | 139 | def copy_config_files(self): 140 | print("Copying config files...") 141 | container = self.container 142 | root = container.name + "/rootfs" 143 | 144 | copyfile(container.path + "/hostname", root + "/etc/hostname") 145 | copyfile(container.path + "/hosts", root + "/etc/hosts") 146 | copyfile(container.path + "/config.lxc", container.name + "/config.lxc.template1") 147 | copyfile(container.path + "/config.env", root + "/.dockerenv") 148 | 149 | sed(container.short_id, container.name, root + "/etc/hostname") 150 | sed(container.short_id, container.name, root + "/etc/hosts") 151 | sed(container.rootfs_path, "{container_path}/rootfs", container.name + "/config.lxc.template1") 152 | 153 | with open(container.name + "/config.lxc.template", "w") as real_template: 154 | with open(container.name + "/config.lxc.template1", "r") as template1: 155 | for line in template1: 156 | if not self.is_allowed_line(line): 157 | real_template.write("#") 158 | real_template.write(line) 159 | 160 | os.remove(container.name + "/config.lxc.template1") 161 | 162 | def is_allowed_line(self, line): 163 | if not line.startswith("lxc.mount.entry"): 164 | return True 165 | for allowed_mount_value in self.allowed_mount_config: 166 | if line.startswith("lxc.mount.entry = " + allowed_mount_value): 167 | return True 168 | return False 169 | 170 | def create_run_script(self): 171 | print("Creating run script...") 172 | content = self.get_run_script_template().replace("{name}", self.container.name) 173 | with open(self.container.name + "/run.sh", "w") as script: 174 | script.write(content) 175 | os.chmod(self.container.name + "/run.sh", 0o755) 176 | 177 | def get_run_script_template(self): 178 | return """#!/bin/bash 179 | 180 | var_path=`pwd` 181 | cp config.lxc.template config.lxc 182 | sed -i "s,{container_path},$var_path,g" config.lxc 183 | 184 | lxc-start -n {name} -f config.lxc -- /.dockerinit -g 172.17.42.1 -i 172.17.0.18/16 -- bash 185 | """ 186 | 187 | def main(): 188 | print("Exporting Docker container to a lxc container") 189 | 190 | parser = argparse.ArgumentParser() 191 | parser.add_argument("docker_container_id", help="Docker container ID to export") 192 | parser.add_argument("new_container_name", help="New container name") 193 | args = parser.parse_args() 194 | 195 | print("Docker container id: ", args.docker_container_id) 196 | print("New container name: ", args.new_container_name) 197 | 198 | container = Container(args.docker_container_id, args.new_container_name) 199 | container.is_valid_container() 200 | 201 | exporter = Exporter(container) 202 | exporter.copy() 203 | 204 | print("\nIt's all done! Now just type:\ncd", args.new_container_name, "&& sudo ./run.sh") 205 | 206 | 207 | if __name__ == "__main__": 208 | main() 209 | --------------------------------------------------------------------------------