From 0108866b3270b985971e25b63e61ddc97e79d5fb Mon Sep 17 00:00:00 2001 From: catborise Date: Wed, 7 Oct 2026 17:54:19 +0300 Subject: [PATCH] build(docker): compilers stay in a build stage, no local secrets enter the image, and the container reports its health --- .dockerignore | 7 ++++- .github/workflows/test.yml | 41 +++++++++++++++++++++++++ Dockerfile | 62 ++++++++++++++++++++++---------------- 3 files changed, 83 insertions(+), 27 deletions(-) diff --git a/.dockerignore b/.dockerignore index 34afaf25..7d92d7c4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -16,8 +16,13 @@ **/env* **/.env* -# Local databases and runtime data +# Local databases and runtime data (secret key, admin password, update backups) **/*.sqlite3* +data +**/*.bak +**/*~ +**/*.swp +**/*.swo **/staticfiles **/*.log **/console/cert.pem* diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f63cee83..cd418995 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -109,5 +109,46 @@ jobs: - name: Checkout Code uses: actions/checkout@v4 + # Files that exist in a used checkout and must never enter the image + - name: Plant Secret Sentinels + run: | + mkdir -p data + for f in data/secret_key data/admin_password webvirtcloud/settings.py.bak \ + 'webvirtcloud/settings.py~' webvirtcloud/.settings.py.swp; do + echo sentinel > "$f" + done + - name: Build Image run: docker build -t webvirtcloud:ci . + + - name: No Runtime Data In Image + run: | + docker run --rm --entrypoint sh webvirtcloud:ci -c ' + cd /srv/webvirtcloud + for f in data/secret_key data/admin_password webvirtcloud/settings.py.bak \ + "webvirtcloud/settings.py~" webvirtcloud/.settings.py.swp; do + if [ -e "$f" ]; then echo "$f is in the image"; exit 1; fi + done' + + # The default profile, and the HTTPS profile behind a TLS proxy + - name: Start Containers + run: | + docker run -d --name wvc -p 127.0.0.1:8080:80 webvirtcloud:ci + docker run -d --name wvc-https -e WEBVIRTCLOUD_HTTPS=1 webvirtcloud:ci + + - name: Wait Until Healthy + run: | + for c in wvc wvc-https; do + for i in $(seq 40); do + [ "$(docker inspect -f '{{.State.Health.Status}}' $c)" = healthy ] && continue 2 + sleep 5 + done + docker inspect -f '{{json .State.Health}}' $c || true + docker exec $c sv status /etc/service/nginx /etc/service/webvirtcloud /etc/service/novnc || true + docker logs $c || true + exit 1 + done + + - name: Import Compiled Dependencies + run: docker exec wvc /srv/webvirtcloud/venv/bin/python3 -c "import libvirt, ldap, lxml.etree, psycopg, sass" + diff --git a/Dockerfile b/Dockerfile index a6871054..9d0f9876 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,3 +1,28 @@ +FROM phusion/baseimage:noble-1.0.2 AS build + +# Compilers and headers for libvirt-python and python-ldap; they stay in this stage. +# hadolint ignore=DL3008 +RUN apt-get update -qqy \ + && DEBIAN_FRONTEND=noninteractive apt-get -qyy install \ + --no-install-recommends \ + python3-venv \ + python3-dev \ + libvirt-dev \ + zlib1g-dev \ + pkg-config \ + gcc \ + libldap2-dev \ + libssl-dev \ + libsasl2-dev \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +WORKDIR /srv/webvirtcloud +COPY conf/requirements.txt conf/requirements.txt +# hadolint ignore=DL3013,DL3042 +RUN python3 -m venv venv \ + && venv/bin/pip install --no-cache-dir -U pip wheel \ + && venv/bin/pip install --no-cache-dir -r conf/requirements.txt + FROM phusion/baseimage:noble-1.0.2 EXPOSE 80 @@ -8,44 +33,24 @@ CMD ["/sbin/my_init"] RUN echo 'APT::Get::Clean=always;' >> /etc/apt/apt.conf.d/99AutomaticClean +# Shared libraries of the compiled wheels, and nginx; no compilers or headers. # hadolint ignore=DL3008 RUN apt-get update -qqy \ && DEBIAN_FRONTEND=noninteractive apt-get -qyy install \ --no-install-recommends \ - git \ - python3-venv \ - python3-pip \ - python3-dev \ - libvirt-dev \ - zlib1g-dev \ + python3 \ nginx \ - pkg-config \ - gcc \ - libldap2-dev \ - libssl-dev \ - libsasl2-dev \ + libvirt0 \ libsasl2-modules \ && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* -# Setup webvirtcloud WORKDIR /srv/webvirtcloud - -# Install Python dependencies first; libvirt-python and python-ldap build from source -COPY conf/requirements.txt conf/requirements.txt -# hadolint ignore=DL3013,DL3042,SC1091 -RUN python3 -m venv venv && \ - . venv/bin/activate && \ - pip3 install --no-cache-dir -U pip wheel && \ - pip3 install --no-cache-dir -r conf/requirements.txt - -# Copy application source +COPY --from=build /srv/webvirtcloud/venv venv COPY . /srv/webvirtcloud # Run collectstatic with temporary dummy key, then remove temporary settings file -# hadolint ignore=SC1091 -RUN . venv/bin/activate && \ - cp webvirtcloud/settings.py.template webvirtcloud/settings.py && \ - SECRET_KEY="build-dummy-key-only-for-collectstatic" python3 manage.py collectstatic --noinput && \ +RUN cp webvirtcloud/settings.py.template webvirtcloud/settings.py && \ + SECRET_KEY="build-dummy-key-only-for-collectstatic" venv/bin/python3 manage.py collectstatic --noinput && \ rm -f webvirtcloud/settings.py && \ chown -R www-data:www-data /srv/webvirtcloud @@ -73,6 +78,11 @@ RUN chmod +x /etc/my_init.d/10_webvirtcloud_init.sh \ /etc/service/novnc/run \ /etc/service/webvirtcloud/run +# X-Forwarded-Proto: https keeps the probe from being redirected when the +# HTTPS profile is on (nginx passes the header on; see conf/nginx). +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD /srv/webvirtcloud/venv/bin/python3 -c "import urllib.request as u; u.urlopen(u.Request('http://127.0.0.1/accounts/login/', headers={'X-Forwarded-Proto': 'https'}), timeout=5)" + # Declare mountable data directory for persistent SQLite and SSH keys VOLUME ["/srv/webvirtcloud/data", "/var/www/.ssh"]