Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
Toggle table of contents sidebar

# Source code for betty.extension.nginx.docker

    """
    Integrate Betty with Docker.
    """

    import asyncio
    from pathlib import Path
    from types import TracebackType
    from typing import cast

    import docker
    from docker.models.containers import Container as DockerContainer

    class Container:
        """
        A Docker container with nginx, configured to serve a Betty site.
        """

        _IMAGE_TAG = "betty-serve"

        def __init__(
            self,
            www_directory_path: Path,
            docker_directory_path: Path,
            nginx_configuration_file_path: Path,
        ):
            self._docker_directory_path = docker_directory_path
            self._nginx_configuration_file_path = nginx_configuration_file_path
            self._www_directory_path = www_directory_path
            self._client = docker.from_env()
            self.__container: DockerContainer | None = None

        async def __aenter__(self) -> None:
            await self.start()

        async def __aexit__(
            self,
            exc_type: type[BaseException] | None,
            exc_val: BaseException | None,
            exc_tb: TracebackType | None,
        ) -> None:
            await self.stop()

        async def start(self) -> None:
            """
            Start the container.
            """
            await asyncio.to_thread(self._start)

        def _start(self) -> None:
            self._client.images.build(
                path=str(self._docker_directory_path), tag=self._IMAGE_TAG
            )
            self._container.start()
            self._container.exec_run(["nginx", "-s", "reload"])

        async def stop(self) -> None:
            """
            Stop the container.
            """
            await asyncio.to_thread(self._stop)

        def _stop(self) -> None:
            if self._container is not None:
                self._container.stop()

        @property
        def _container(self) -> DockerContainer:
            if self.__container is None:
                self.__container = self._client.containers.create(
                    self._IMAGE_TAG,
                    auto_remove=True,
                    detach=True,
                    volumes={
                        self._nginx_configuration_file_path: {
                            "bind": "/etc/nginx/conf.d/betty.conf",
                            "mode": "ro",
                        },
                        self._www_directory_path: {
                            "bind": "/var/www/betty",
                            "mode": "ro",
                        },
                    },
                )
            return self.__container

        @property
        def ip(self) -> str:
            """
            The container's public IP address.
            """
            return cast(
                str,
                self._client.api.inspect_container(self._container.id)["NetworkSettings"][
                    "Networks"
                ]["bridge"]["IPAddress"],
            )