feat: pre bundled dockergen w/ nginx-proxy template image

This commit is contained in:
Nicolas Duchon 2024-12-18 20:52:50 +01:00
parent c60eff5d16
commit a67aff92e9
7 changed files with 131 additions and 31 deletions

View file

@ -23,7 +23,7 @@ jobs:
name: Build and publish image name: Build and publish image
strategy: strategy:
matrix: matrix:
base: [alpine, debian] flavor: [alpine, debian, dockergen]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
@ -37,7 +37,7 @@ jobs:
- name: Retrieve docker-gen version - name: Retrieve docker-gen version
id: docker-gen_version id: docker-gen_version
run: sed -n -e 's;^FROM nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.base }} >> "$GITHUB_OUTPUT" run: sed -n -e 's;^FROM docker.io/nginxproxy/docker-gen:\([0-9.]*\).*;VERSION=\1;p' Dockerfile.${{ matrix.flavor }} >> "$GITHUB_OUTPUT"
- name: Get Docker tags - name: Get Docker tags
id: docker_meta id: docker_meta
@ -48,12 +48,15 @@ jobs:
nginxproxy/nginx-proxy nginxproxy/nginx-proxy
jwilder/nginx-proxy jwilder/nginx-proxy
tags: | tags: |
type=semver,pattern={{version}},enable=${{ matrix.base == 'debian' }} type=semver,pattern={{version}},enable=${{ matrix.flavor == 'debian' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'debian' }} type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'debian' }}
type=semver,suffix=-alpine,pattern={{version}},enable=${{ matrix.base == 'alpine' }} type=semver,suffix=-alpine,pattern={{version}},enable=${{ matrix.flavor == 'alpine' }}
type=semver,suffix=-alpine,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'alpine' }} type=semver,suffix=-alpine,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'alpine' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'debian' }} type=semver,suffix=-dockergen,pattern={{version}},enable=${{ matrix.flavor == 'dockergen' }}
type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'alpine' }} type=semver,suffix=-dockergen,pattern={{major}}.{{minor}},enable=${{ matrix.flavor == 'dockergen' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'debian' }}
type=raw,value=alpine,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'alpine' }}
type=raw,value=dockergen,enable=${{ github.ref == 'refs/heads/main' && matrix.flavor == 'dockergen' }}
labels: | labels: |
org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder org.opencontainers.image.authors=Nicolas Duchon <nicolas.duchon@gmail.com> (@buchdag), Jason Wilder
org.opencontainers.image.version=${{ steps.nginx-proxy_version.outputs.VERSION }} org.opencontainers.image.version=${{ steps.nginx-proxy_version.outputs.VERSION }}
@ -84,7 +87,7 @@ jobs:
uses: docker/build-push-action@v6 uses: docker/build-push-action@v6
with: with:
context: . context: .
file: Dockerfile.${{ matrix.base }} file: Dockerfile.${{ matrix.flavor }}
build-args: | build-args: |
NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }} NGINX_PROXY_VERSION=${{ steps.nginx-proxy_version.outputs.VERSION }}
DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }} DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }}

View file

@ -20,7 +20,7 @@ jobs:
strategy: strategy:
matrix: matrix:
base_docker_image: [alpine, debian] flavor: [alpine, debian, dockergen]
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -43,8 +43,10 @@ jobs:
run: make build-webserver run: make build-webserver
- name: Build Docker nginx proxy test image - name: Build Docker nginx proxy test image
run: make build-nginx-proxy-test-${{ matrix.base_docker_image }} run: make build-nginx-proxy-test-${{ matrix.flavor }}
- name: Run tests - name: Run tests
run: pytest run: pytest
working-directory: test working-directory: test
env:
COMPOSE_PROFILES: ${{ matrix.flavor == 'dockergen' && 'separateContainers' || 'singleContainer' }}

18
Dockerfile.dockergen Normal file
View file

@ -0,0 +1,18 @@
FROM docker.io/nginxproxy/docker-gen:0.14.4
ARG NGINX_PROXY_VERSION
ENV NGINX_PROXY_VERSION=${NGINX_PROXY_VERSION} \
DOCKER_HOST=unix:///tmp/docker.sock \
DHPARAM_SKIP=true \
NGINX_CONTAINER_NAME=nginx-proxy
# Install dependencies
RUN apk add --no-cache --virtual .run-deps bash
RUN mkdir -p '/etc/nginx/conf.d'
COPY app nginx.tmpl LICENSE /app/
WORKDIR /app/
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["docker-gen", "-notify-sighup", "$NGINX_CONTAINER_NAME", "-watch", "/app/nginx.tmpl", "/etc/nginx/conf.d/default.conf"]

View file

@ -11,10 +11,19 @@ build-nginx-proxy-test-debian:
build-nginx-proxy-test-alpine: build-nginx-proxy-test-alpine:
docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test . docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.alpine -t nginxproxy/nginx-proxy:test .
build-nginx-proxy-test-dockergen:
docker build --pull --build-arg NGINX_PROXY_VERSION="test" -f Dockerfile.dockergen -t nginxproxy/nginx-proxy:test-dockergen .
test-debian: export COMPOSE_PROFILES = singleContainer
test-debian: build-webserver build-nginx-proxy-test-debian test-debian: build-webserver build-nginx-proxy-test-debian
test/pytest.sh test/pytest.sh
test-alpine: export COMPOSE_PROFILES = singleContainer
test-alpine: build-webserver build-nginx-proxy-test-alpine test-alpine: build-webserver build-nginx-proxy-test-alpine
test/pytest.sh test/pytest.sh
test: test-debian test-alpine test-dockergen: export COMPOSE_PROFILES = separateContainers
test-dockergen: build-webserver build-nginx-proxy-test-docker-gen
test/pytest.sh
test: test-debian test-alpine test-dockergen

View file

@ -101,7 +101,7 @@ function _setup_dhparam() {
} }
# Run the init logic if the default CMD was provided # Run the init logic if the default CMD was provided
if [[ $* == 'forego start -r' ]]; then if [[ $* == "forego start -r" ]] || [[ $* =~ "docker-gen -notify-sighup" ]]; then
_print_version _print_version
_check_unix_socket _check_unix_socket
@ -116,6 +116,11 @@ if [[ $* == 'forego start -r' ]]; then
Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true". Warning: The default value of TRUST_DOWNSTREAM_PROXY might change to "false" in a future version of nginx-proxy. If you require TRUST_DOWNSTREAM_PROXY to be enabled, explicitly set it to "true".
EOT EOT
fi fi
if [[ $3 == "\$NGINX_CONTAINER_NAME" && -n "$NGINX_CONTAINER_NAME" ]]; then
# change the value of $3 to the expanded $NGINX_CONTAINER_NAME variable
set -- "${@:1:2}" "$NGINX_CONTAINER_NAME" "${@:4}"
fi
fi fi
exec "$@" exec "$@"

View file

@ -27,6 +27,9 @@ PYTEST_RUNNING_IN_CONTAINER = os.environ.get('PYTEST_RUNNING_IN_CONTAINER') == "
FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4 FORCE_CONTAINER_IPV6 = False # ugly global state to consider containers' IPv6 address instead of IPv4
DOCKER_COMPOSE = os.environ.get('DOCKER_COMPOSE', 'docker compose') DOCKER_COMPOSE = os.environ.get('DOCKER_COMPOSE', 'docker compose')
COMPOSE_PROFILES = os.environ.get('COMPOSE_PROFILES', 'singleContainer')
IMAGE_TAG = "test-dockergen" if COMPOSE_PROFILES == "separateContainers" else "test"
docker_client = docker.from_env() docker_client = docker.from_env()
@ -75,11 +78,11 @@ class requests_for_docker(object):
""" """
Return list of containers Return list of containers
""" """
nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"}) nginx_proxy_containers = docker_client.containers.list(filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
if len(nginx_proxy_containers) > 1: if len(nginx_proxy_containers) > 1:
pytest.fail("Too many running nginxproxy/nginx-proxy:test containers", pytrace=False) pytest.fail(f"Too many running nginxproxy/nginx-proxy:{IMAGE_TAG} containers", pytrace=False)
elif len(nginx_proxy_containers) == 0: elif len(nginx_proxy_containers) == 0:
pytest.fail("No running nginxproxy/nginx-proxy:test container", pytrace=False) pytest.fail(f"No running nginxproxy/nginx-proxy:{IMAGE_TAG} container", pytrace=False)
return nginx_proxy_containers return nginx_proxy_containers
def get_conf(self): def get_conf(self):
@ -188,7 +191,7 @@ def container_ipv6(container):
return net_info[network_name]["GlobalIPv6Address"] return net_info[network_name]["GlobalIPv6Address"]
def nginx_proxy_dns_resolver(domain_name): def nginx_proxy_single_container_dns_resolver(domain_name):
""" """
if "nginx-proxy" if found in host, return the ip address of the docker container if "nginx-proxy" if found in host, return the ip address of the docker container
issued from the docker image nginxproxy/nginx-proxy:test. issued from the docker image nginxproxy/nginx-proxy:test.
@ -196,21 +199,44 @@ def nginx_proxy_dns_resolver(domain_name):
:return: IP or None :return: IP or None
""" """
log = logging.getLogger('DNS') log = logging.getLogger('DNS')
log.debug(f"nginx_proxy_dns_resolver({domain_name!r})") log.debug(f"nginx_proxy_single_container_dns_resolver({domain_name!r})")
if 'nginx-proxy' in domain_name: if 'nginx-proxy' in domain_name:
nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"}) nginxproxy_containers = docker_client.containers.list(filters={"status": "running", "ancestor": "nginxproxy/nginx-proxy:test"})
if len(nginxproxy_containers) == 0: if len(nginxproxy_containers) == 0:
log.warn(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}") log.info(f"no container found from image nginxproxy/nginx-proxy:test while resolving {domain_name!r}")
exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"}) exited_nginxproxy_containers = docker_client.containers.list(filters={"status": "exited", "ancestor": "nginxproxy/nginx-proxy:test"})
if len(exited_nginxproxy_containers) > 0: if len(exited_nginxproxy_containers) > 0:
exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs() exited_nginxproxy_container_logs = exited_nginxproxy_containers[0].logs()
log.warn(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode()) log.warning(f"nginxproxy/nginx-proxy:test container might have exited unexpectedly. Container logs: " + "\n" + exited_nginxproxy_container_logs.decode())
return return
nginxproxy_container = nginxproxy_containers[0] nginxproxy_container = nginxproxy_containers[0]
ip = container_ip(nginxproxy_container) ip = container_ip(nginxproxy_container)
log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}") log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx-proxy container {nginxproxy_container.name}")
return ip return ip
def nginx_proxy_separate_containers_dns_resolver(domain_name):
"""
if "nginx-proxy" if found in host, return the ip address of the docker container
labeled with "com.github.nginx-proxy.nginx-proxy.nginx".
:return: IP or None
"""
log = logging.getLogger('DNS')
log.debug(f"nginx_proxy_separate_containers_dns_resolver({domain_name!r})")
if 'nginx-proxy' in domain_name:
nginx_containers = docker_client.containers.list(filters={"status": "running", "label": "com.github.nginx-proxy.nginx-proxy.nginx"})
if len(nginx_containers) == 0:
log.info(f"no container labeled with com.github.nginx-proxy.nginx-proxy.nginx found while resolving {domain_name!r}")
exited_nginx_containers = docker_client.containers.list(filters={"status": "exited", "label": "com.github.nginx-proxy.nginx-proxy.nginx"})
if len(exited_nginx_containers) > 0:
exited_nginx_container_logs = exited_nginx_containers[0].logs()
log.warning(f"nginx container might have exited unexpectedly. Container logs: " + "\n" + exited_nginx_container_logs.decode())
return
nginx_container = nginx_containers[0]
ip = container_ip(nginx_container)
log.info(f"resolving domain name {domain_name!r} as IP address {ip} of nginx container {nginx_container.name}")
return ip
def docker_container_dns_resolver(domain_name): def docker_container_dns_resolver(domain_name):
""" """
if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container if domain name is of the form "XXX.container.docker" or "anything.XXX.container.docker", return the ip address of the docker container
@ -231,7 +257,7 @@ def docker_container_dns_resolver(domain_name):
try: try:
container = docker_client.containers.get(container_name) container = docker_client.containers.get(container_name)
except docker.errors.NotFound: except docker.errors.NotFound:
log.warn(f"container named {container_name!r} not found while resolving {domain_name!r}") log.warning(f"container named {container_name!r} not found while resolving {domain_name!r}")
return return
log.debug(f"container {container.name!r} found ({container.short_id})") log.debug(f"container {container.name!r} found ({container.short_id})")
@ -244,7 +270,8 @@ def monkey_patch_urllib_dns_resolver():
""" """
Alter the behavior of the urllib DNS resolver so that any domain name Alter the behavior of the urllib DNS resolver so that any domain name
containing substring 'nginx-proxy' will resolve to the IP address containing substring 'nginx-proxy' will resolve to the IP address
of the container created from image 'nginxproxy/nginx-proxy:test'. of the container created from image 'nginxproxy/nginx-proxy:test' or
labeled with 'com.github.nginx-proxy.nginx-proxy.nginx'.
""" """
prv_getaddrinfo = socket.getaddrinfo prv_getaddrinfo = socket.getaddrinfo
dns_cache = {} dns_cache = {}
@ -258,7 +285,9 @@ def monkey_patch_urllib_dns_resolver():
pytest.skip("This system does not support IPv6") pytest.skip("This system does not support IPv6")
# custom DNS resolvers # custom DNS resolvers
ip = nginx_proxy_dns_resolver(args[0]) ip = nginx_proxy_single_container_dns_resolver(args[0])
if ip is None:
ip = nginx_proxy_separate_containers_dns_resolver(args[0])
if ip is None: if ip is None:
ip = docker_container_dns_resolver(args[0]) ip = docker_container_dns_resolver(args[0])
if ip is not None: if ip is not None:
@ -319,10 +348,11 @@ def docker_compose_down(compose_file='docker-compose.yml'):
def wait_for_nginxproxy_to_be_ready(): def wait_for_nginxproxy_to_be_ready():
""" """
If one (and only one) container started from image nginxproxy/nginx-proxy:test is found, If one (and only one) container started from image nginxproxy/nginx-proxy:test
wait for its log to contain substring "Watching docker events" or nginxproxy/nginx-proxy:test-dockergen is found, wait for its log to contain
substring "Watching docker events"
""" """
containers = docker_client.containers.list(filters={"ancestor": "nginxproxy/nginx-proxy:test"}) containers = docker_client.containers.list(filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
if len(containers) != 1: if len(containers) != 1:
return return
container = containers[0] container = containers[0]
@ -371,7 +401,7 @@ def connect_to_network(network):
try: try:
my_container = docker_client.containers.get(test_container) my_container = docker_client.containers.get(test_container)
except docker.errors.NotFound: except docker.errors.NotFound:
logging.warn(f"container {test_container} not found") logging.warning(f"container {test_container} not found")
return return
# figure out our container networks # figure out our container networks
@ -399,7 +429,7 @@ def disconnect_from_network(network=None):
try: try:
my_container = docker_client.containers.get(test_container) my_container = docker_client.containers.get(test_container)
except docker.errors.NotFound: except docker.errors.NotFound:
logging.warn(f"container {test_container} not found") logging.warning(f"container {test_container} not found")
return return
# figure out our container networks # figure out our container networks
@ -527,7 +557,11 @@ def acme_challenge_path():
def pytest_runtest_logreport(report): def pytest_runtest_logreport(report):
if report.failed: if report.failed:
if isinstance(report.longrepr, ReprExceptionInfo): if isinstance(report.longrepr, ReprExceptionInfo):
test_containers = docker_client.containers.list(all=True, filters={"ancestor": "nginxproxy/nginx-proxy:test"}) nginx_containers = docker_client.containers.list(all=True, filters={"label": "com.github.nginx-proxy.nginx-proxy.nginx"})
for container in nginx_containers:
report.longrepr.addsection('nginx container logs', container.logs())
test_containers = docker_client.containers.list(all=True, filters={"ancestor": f"nginxproxy/nginx-proxy:{IMAGE_TAG}"})
for container in test_containers: for container in test_containers:
report.longrepr.addsection('nginx-proxy logs', container.logs()) report.longrepr.addsection('nginx-proxy logs', container.logs())
report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container)) report.longrepr.addsection('nginx-proxy conf', get_nginx_conf_from_container(container))
@ -553,9 +587,9 @@ def pytest_runtest_setup(item):
############################################################################### ###############################################################################
try: try:
docker_client.images.get('nginxproxy/nginx-proxy:test') docker_client.images.get(f"nginxproxy/nginx-proxy:{IMAGE_TAG}")
except docker.errors.ImageNotFound: except docker.errors.ImageNotFound:
pytest.exit("The docker image 'nginxproxy/nginx-proxy:test' is missing") pytest.exit(f"The docker image 'nginxproxy/nginx-proxy:{IMAGE_TAG}' is missing")
if Version(docker.__version__) < Version("7.0.0"): if Version(docker.__version__) < Version("7.0.0"):
pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later") pytest.exit("This test suite is meant to work with the python docker module v7.0.0 or later")

View file

@ -2,8 +2,13 @@ networks:
netA: netA:
netB: netB:
volumes:
nginx_conf:
services: services:
reverseproxy: reverseproxy:
profiles:
- singleContainer
container_name: reverseproxy container_name: reverseproxy
networks: networks:
- netA - netA
@ -11,6 +16,30 @@ services:
volumes: volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro - /var/run/docker.sock:/tmp/docker.sock:ro
reverseproxynginx:
profiles:
- separateContainers
container_name: reverseproxy
networks:
- netA
image: nginx:alpine
volumes:
- nginx_conf:/etc/nginx/conf.d:ro
labels:
- "com.github.nginx-proxy.nginx-proxy.nginx"
docker-gen:
profiles:
- separateContainers
networks:
- netA
image: nginxproxy/nginx-proxy:test-dockergen
volumes:
- /var/run/docker.sock:/tmp/docker.sock:ro
- nginx_conf:/etc/nginx/conf.d
environment:
NGINX_CONTAINER_NAME: reverseproxy
webA: webA:
networks: networks:
- netA - netA