mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Redo docker build system with buildkit+multi-stage and cache pio packages (#2338)
This commit is contained in:
		| @@ -1,5 +1,55 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-base:latest | ||||
| FROM ${BUILD_FROM} | ||||
| # Build these with the build.py script | ||||
| # Example: | ||||
| #   python3 docker/build.py --tag dev --arch amd64 --build-type docker build | ||||
|  | ||||
| # One of "docker", "hassio" | ||||
| ARG BASEIMGTYPE=docker | ||||
|  | ||||
| FROM ghcr.io/hassio-addons/debian-base/amd64:5.0.0 AS base-hassio-amd64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/aarch64:5.0.0 AS base-hassio-arm64 | ||||
| FROM ghcr.io/hassio-addons/debian-base/armv7:5.0.0 AS base-hassio-armv7 | ||||
| FROM debian:bullseye-20210816-slim AS base-docker-amd64 | ||||
| FROM debian:bullseye-20210816-slim AS base-docker-arm64 | ||||
| FROM debian:bullseye-20210816-slim AS base-docker-armv7 | ||||
|  | ||||
| # Use TARGETARCH/TARGETVARIANT defined by docker | ||||
| # https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope | ||||
| FROM base-${BASEIMGTYPE}-${TARGETARCH}${TARGETVARIANT} AS base | ||||
|  | ||||
| RUN \ | ||||
|     apt-get update \ | ||||
|     # Use pinned versions so that we get updates with build caching | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         python3=3.9.2-3 \ | ||||
|         python3-pip=20.3.4-4 \ | ||||
|         python3-setuptools=52.0.0-4 \ | ||||
|         python3-pil=8.1.2+dfsg-0.3 \ | ||||
|         python3-cryptography=3.3.2-1 \ | ||||
|         iputils-ping=3:20210202-1 \ | ||||
|         git=1:2.30.2-1 \ | ||||
|         curl=7.74.0-1.3+b1 \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /var/lib/apt/lists/* | ||||
|  | ||||
| ENV \ | ||||
|   # Fix click python3 lang warning https://click.palletsprojects.com/en/7.x/python3/ | ||||
|   LANG=C.UTF-8 LC_ALL=C.UTF-8 \ | ||||
|   # Store globally installed pio libs in /piolibs | ||||
|   PLATFORMIO_GLOBALLIB_DIR=/piolibs | ||||
|  | ||||
| RUN \ | ||||
|     # Ubuntu python3-pip is missing wheel | ||||
|     pip3 install --no-cache-dir \ | ||||
|         wheel==0.36.2 \ | ||||
|         platformio==5.2.0 \ | ||||
|     # Change some platformio settings | ||||
|     && platformio settings set enable_telemetry No \ | ||||
|     && platformio settings set check_libraries_interval 1000000 \ | ||||
|     && platformio settings set check_platformio_interval 1000000 \ | ||||
|     && platformio settings set check_platforms_interval 1000000 \ | ||||
|     && mkdir -p /piolibs | ||||
|  | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | ||||
| @@ -7,9 +57,14 @@ RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| # Then copy esphome and install | ||||
| COPY . . | ||||
| RUN pip3 install --no-cache-dir -e . | ||||
|  | ||||
|  | ||||
| # ======================= docker-type image ======================= | ||||
| FROM base AS docker | ||||
|  | ||||
| # Copy esphome and install | ||||
| COPY . /esphome | ||||
| RUN pip3 install --no-cache-dir -e /esphome | ||||
|  | ||||
| # Settings for dashboard | ||||
| ENV USERNAME="" PASSWORD="" | ||||
| @@ -17,14 +72,74 @@ ENV USERNAME="" PASSWORD="" | ||||
| # Expose the dashboard to Docker | ||||
| EXPOSE 6052 | ||||
|  | ||||
| # Run healthcheck (heartbeat) | ||||
| HEALTHCHECK --interval=30s --timeout=30s \ | ||||
|   CMD curl --fail http://localhost:6052 || exit 1 | ||||
| COPY docker/docker_entrypoint.sh /entrypoint.sh | ||||
|  | ||||
| # The directory the user should mount their configuration files to | ||||
| VOLUME /config | ||||
| WORKDIR /config | ||||
| # Set entrypoint to esphome so that the user doesn't have to type 'esphome' | ||||
| # Set entrypoint to esphome (via a script) so that the user doesn't have to type 'esphome' | ||||
| # in every docker command twice | ||||
| ENTRYPOINT ["esphome"] | ||||
| ENTRYPOINT ["/entrypoint.sh"] | ||||
| # When no arguments given, start the dashboard in the workdir | ||||
| CMD ["dashboard", "/config"] | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # ======================= hassio-type image ======================= | ||||
| FROM base AS hassio | ||||
|  | ||||
| RUN \ | ||||
|     apt-get update \ | ||||
|     # Use pinned versions so that we get updates with build caching | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         nginx=1.18.0-6.1 \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /var/lib/apt/lists/* | ||||
|  | ||||
| ARG BUILD_VERSION=dev | ||||
|  | ||||
| # Copy root filesystem | ||||
| COPY docker/hassio-rootfs/ / | ||||
|  | ||||
| # Copy esphome and install | ||||
| COPY . /esphome | ||||
| RUN pip3 install --no-cache-dir -e /esphome | ||||
|  | ||||
| # Labels | ||||
| LABEL \ | ||||
|     io.hass.name="ESPHome" \ | ||||
|     io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ | ||||
|     io.hass.type="addon" \ | ||||
|     io.hass.version="${BUILD_VERSION}" | ||||
|     # io.hass.arch is inherited from addon-debian-base | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| # ======================= lint-type image ======================= | ||||
| FROM base AS lint | ||||
|  | ||||
| ENV \ | ||||
|   PLATFORMIO_CORE_DIR=/esphome/.temp/platformio | ||||
|  | ||||
| RUN \ | ||||
|     apt-get update \ | ||||
|     # Use pinned versions so that we get updates with build caching | ||||
|     && apt-get install -y --no-install-recommends \ | ||||
|         clang-format-11=1:11.0.1-2 \ | ||||
|         clang-tidy-11=1:11.0.1-2 \ | ||||
|         patch=2.7.6-7 \ | ||||
|         software-properties-common=0.96.20.2-2.1 \ | ||||
|         nano=5.4-2 \ | ||||
|         build-essential=12.9 \ | ||||
|         python3-dev=3.9.2-3 \ | ||||
|     && rm -rf \ | ||||
|         /tmp/* \ | ||||
|         /var/{cache,log}/* \ | ||||
|         /var/lib/apt/lists/* | ||||
|  | ||||
| VOLUME ["/esphome"] | ||||
| WORKDIR /esphome | ||||
|   | ||||
| @@ -1 +0,0 @@ | ||||
| FROM esphome/esphome-lint:1.2 | ||||
| @@ -1,25 +0,0 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-hassio-base:latest | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| # First install requirements to leverage caching when requirements don't change | ||||
| COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| # Copy root filesystem | ||||
| COPY docker/rootfs/ / | ||||
|  | ||||
| # Then copy esphome and install | ||||
| COPY . /opt/esphome/ | ||||
| RUN pip3 install --no-cache-dir -e /opt/esphome | ||||
|  | ||||
| # Build arguments | ||||
| ARG BUILD_VERSION=dev | ||||
|  | ||||
| # Labels | ||||
| LABEL \ | ||||
|     io.hass.name="ESPHome" \ | ||||
|     io.hass.description="Manage and program ESP8266/ESP32 microcontrollers through YAML configuration files" \ | ||||
|     io.hass.type="addon" \ | ||||
|     io.hass.version=${BUILD_VERSION} | ||||
| @@ -1,10 +0,0 @@ | ||||
| ARG BUILD_FROM=esphome/esphome-lint-base:latest | ||||
| FROM ${BUILD_FROM} | ||||
|  | ||||
| COPY requirements.txt requirements_optional.txt requirements_test.txt docker/platformio_install_deps.py  platformio.ini / | ||||
| RUN \ | ||||
|     pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt -r /requirements_test.txt \ | ||||
|     && /platformio_install_deps.py /platformio.ini | ||||
|  | ||||
| VOLUME ["/esphome"] | ||||
| WORKDIR /esphome | ||||
| @@ -2,7 +2,7 @@ | ||||
| from dataclasses import dataclass | ||||
| import subprocess | ||||
| import argparse | ||||
| import platform | ||||
| from platform import machine | ||||
| import shlex | ||||
| import re | ||||
| import sys | ||||
| @@ -24,9 +24,6 @@ TYPE_LINT = 'lint' | ||||
| TYPES = [TYPE_DOCKER, TYPE_HA_ADDON, TYPE_LINT] | ||||
|  | ||||
|  | ||||
| BASE_VERSION = "4.2.0" | ||||
|  | ||||
|  | ||||
| parser = argparse.ArgumentParser() | ||||
| parser.add_argument("--tag", type=str, required=True, help="The main docker tag to push to. If a version number also adds latest and/or beta tag") | ||||
| parser.add_argument("--arch", choices=ARCHS, required=False, help="The architecture to build for") | ||||
| @@ -34,27 +31,17 @@ parser.add_argument("--build-type", choices=TYPES, required=True, help="The type | ||||
| parser.add_argument("--dry-run", action="store_true", help="Don't run any commands, just print them") | ||||
| subparsers = parser.add_subparsers(help="Action to perform", dest="command", required=True) | ||||
| build_parser = subparsers.add_parser("build", help="Build the image") | ||||
| push_parser = subparsers.add_parser("push", help="Tag the already built image and push it to docker hub") | ||||
| build_parser.add_argument("--push", help="Also push the images") | ||||
| manifest_parser = subparsers.add_parser("manifest", help="Create a manifest from already pushed images") | ||||
|  | ||||
|  | ||||
|  | ||||
| # only lists some possibilities, doesn't have to be perfect | ||||
| # https://stackoverflow.com/a/45125525 | ||||
| UNAME_TO_ARCH = { | ||||
|     "x86_64": ARCH_AMD64, | ||||
|     "aarch64": ARCH_AARCH64, | ||||
|     "aarch64_be": ARCH_AARCH64, | ||||
|     "arm": ARCH_ARMV7, | ||||
| } | ||||
|  | ||||
|  | ||||
| @dataclass(frozen=True) | ||||
| class DockerParams: | ||||
|     build_from: str | ||||
|     build_to: str | ||||
|     manifest_to: str | ||||
|     dockerfile: str | ||||
|     baseimgtype: str | ||||
|     platform: str | ||||
|     target: str | ||||
|  | ||||
|     @classmethod | ||||
|     def for_type_arch(cls, build_type, arch): | ||||
| @@ -63,18 +50,28 @@ class DockerParams: | ||||
|             TYPE_HA_ADDON: "esphome/esphome-hassio", | ||||
|             TYPE_LINT: "esphome/esphome-lint" | ||||
|         }[build_type] | ||||
|         build_from = f"ghcr.io/{prefix}-base-{arch}:{BASE_VERSION}" | ||||
|         build_to = f"{prefix}-{arch}" | ||||
|         dockerfile = { | ||||
|             TYPE_DOCKER: "docker/Dockerfile", | ||||
|             TYPE_HA_ADDON: "docker/Dockerfile.hassio", | ||||
|             TYPE_LINT: "docker/Dockerfile.lint", | ||||
|         baseimgtype = { | ||||
|             TYPE_DOCKER: "docker", | ||||
|             TYPE_HA_ADDON: "hassio", | ||||
|             TYPE_LINT: "docker", | ||||
|         }[build_type] | ||||
|         platform = { | ||||
|             ARCH_AMD64: "linux/amd64", | ||||
|             ARCH_ARMV7: "linux/arm/v7", | ||||
|             ARCH_AARCH64: "linux/arm64", | ||||
|         }[arch] | ||||
|         target = { | ||||
|             TYPE_DOCKER: "docker", | ||||
|             TYPE_HA_ADDON: "hassio", | ||||
|             TYPE_LINT: "lint", | ||||
|         }[build_type] | ||||
|         return cls( | ||||
|             build_from=build_from, | ||||
|             build_to=build_to, | ||||
|             manifest_to=prefix, | ||||
|             dockerfile=dockerfile | ||||
|             baseimgtype=baseimgtype, | ||||
|             platform=platform, | ||||
|             target=target, | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @@ -117,41 +114,26 @@ def main(): | ||||
|             CHANNEL_RELEASE: "latest", | ||||
|         }[channel] | ||||
|         cache_img = f"ghcr.io/{params.build_to}:{cache_tag}" | ||||
|         run_command("docker", "pull", cache_img, ignore_error=True) | ||||
|  | ||||
|         # 2. register QEMU binfmt (if not host arch) | ||||
|         is_native = UNAME_TO_ARCH.get(platform.machine()) == args.arch | ||||
|         if not is_native: | ||||
|             run_command( | ||||
|                 "docker", "run", "--rm", "--privileged", "multiarch/qemu-user-static:5.2.0-2", | ||||
|                 "--reset", "-p", "yes" | ||||
|             ) | ||||
|  | ||||
|         # 3. build | ||||
|         run_command( | ||||
|             "docker", "build", | ||||
|             "--build-arg", f"BUILD_FROM={params.build_from}", | ||||
|             "--build-arg", f"BUILD_VERSION={args.tag}", | ||||
|             "--tag", f"{params.build_to}:{args.tag}", | ||||
|             "--cache-from", cache_img, | ||||
|             "--file", params.dockerfile, | ||||
|             "." | ||||
|         ) | ||||
|     elif args.command == "push": | ||||
|         params = DockerParams.for_type_arch(args.build_type, args.arch) | ||||
|         imgs = [f"{params.build_to}:{tag}" for tag in tags_to_push] | ||||
|         imgs += [f"ghcr.io/{params.build_to}:{tag}" for tag in tags_to_push] | ||||
|         src = imgs[0] | ||||
|         # 1. tag images | ||||
|         for img in imgs[1:]: | ||||
|             run_command( | ||||
|                 "docker", "tag", src, img | ||||
|             ) | ||||
|         # 2. push images | ||||
|  | ||||
|         # 3. build | ||||
|         cmd = [ | ||||
|             "docker", "buildx", "build", | ||||
|             "--build-arg", f"BASEIMGTYPE={params.baseimgtype}", | ||||
|             "--build-arg", f"BUILD_VERSION={args.tag}", | ||||
|             "--cache-from", cache_img, | ||||
|             "--file", "docker/Dockerfile", | ||||
|             "--platform", params.platform, | ||||
|             "--target", params.target, | ||||
|         ] | ||||
|         for img in imgs: | ||||
|             run_command( | ||||
|                 "docker", "push", img | ||||
|             ) | ||||
|             cmd += ["--tag", img] | ||||
|         if args.push: | ||||
|             cmd.append("--push") | ||||
|  | ||||
|         run_command(*cmd, ".") | ||||
|     elif args.command == "manifest": | ||||
|         manifest = DockerParams.for_type_arch(args.build_type, ARCH_AMD64).manifest_to | ||||
|  | ||||
|   | ||||
							
								
								
									
										18
									
								
								docker/docker_entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										18
									
								
								docker/docker_entrypoint.sh
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #!/bin/bash | ||||
|  | ||||
| # If /cache is mounted, use that as PIO's coredir | ||||
| # otherwise use path in /config (so that PIO packages aren't downloaded on each compile) | ||||
|  | ||||
| if [[ -d /cache ]]; then | ||||
|     export PLATFORMIO_CORE_DIR=/cache/platformio | ||||
| else | ||||
|     export PLATFORMIO_CORE_DIR=/config/.esphome/platformio | ||||
| fi | ||||
|  | ||||
| if [[ ! -d "${PLATFORMIO_CORE_DIR}" ]]; then | ||||
|     echo "Creating cache directory ${PLATFORMIO_CORE_DIR}" | ||||
|     echo "You can change this behavior by mounting a directory to the container's /cache directory." | ||||
|     mkdir -p "${PLATFORMIO_CORE_DIR}" | ||||
| fi | ||||
|  | ||||
| exec esphome "$@" | ||||
							
								
								
									
										9
									
								
								docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								docker/hassio-rootfs/etc/cont-init.d/30-dirs.sh
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #!/usr/bin/with-contenv bashio | ||||
| # ============================================================================== | ||||
| # Community Hass.io Add-ons: ESPHome | ||||
| # This files creates all directories used by esphome | ||||
| # ============================================================================== | ||||
|  | ||||
| PLATFORMIO_CORE_DIR=/data/cache/platformio | ||||
|  | ||||
| mkdir -p "${PLATFORMIO_CORE_DIR}" | ||||
| @@ -22,5 +22,8 @@ if bashio::config.has_value 'relative_url'; then | ||||
|     export ESPHOME_DASHBOARD_RELATIVE_URL=$(bashio::config 'relative_url') | ||||
| fi | ||||
| 
 | ||||
| export PLATFORMIO_CORE_DIR=/data/cache/platformio | ||||
| export PLATFORMIO_GLOBALLIB_DIR=/piolibs | ||||
| 
 | ||||
| bashio::log.info "Starting ESPHome dashboard..." | ||||
| exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio | ||||
		Reference in New Issue
	
	Block a user