Repository: pi-hole/docker-pi-hole Branch: master Commit: ac202988906f Files: 40 Total size: 77.0 KB Directory structure: gitextract_w4fbfg1b/ ├── .codespellignore ├── .editorconfig ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── config.yml │ ├── PULL_REQUEST_TEMPLATE.md │ ├── dco.yml │ ├── dependabot.yml │ ├── release.yml │ └── workflows/ │ ├── build-and-publish.yml │ ├── build-and-test.yml │ ├── codespell.yml │ ├── dockerhub-description.yml │ ├── editorconfig.yml │ ├── housekeeping.yml │ ├── merge-conflict.yml │ ├── stale.yml │ ├── stale_pr.yml │ └── sync-back-to-dev.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.sh ├── examples/ │ ├── Caddyfile │ └── docker-compose-caddy-proxy.yml ├── src/ │ ├── .dockerignore │ ├── Dockerfile │ ├── bash_functions.sh │ ├── crontab.txt │ └── start.sh └── test/ ├── TESTING.md ├── requirements.txt ├── tests/ │ ├── __init__.py │ ├── conftest.py │ ├── test_bash_functions.py │ └── test_general.py └── tox.ini ================================================ FILE CONTENTS ================================================ ================================================ FILE: .codespellignore ================================================ padd ================================================ FILE: .editorconfig ================================================ # EditorConfig is awesome: https://editorconfig.org/ # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] end_of_line = lf insert_final_newline = true indent_style = space indent_size = tab tab_width = 4 charset = utf-8 trim_trailing_whitespace = true [*.yml] tab_width = 2 [*.md] tab_width = 2 ================================================ FILE: .github/CODEOWNERS ================================================ # see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, * @pi-hole/docker-maintainers ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- This is a: **FILL ME IN** ## Details ## Related Issues - [ ] I have searched this repository/Pi-hole forums for existing issues and pull requests that look similar ## How to reproduce the issue 1. Environment data * Operating System: **ENTER HERE** * Hardware: * Kernel Architecture: * Docker Install Info and version: - Software source: - Supplimentary Software: * Hardware architecture: 2. docker-compose.yml contents, docker run shell command, or paste a screenshot of any UI based configuration of containers here 3. any additional info to help reproduce ## These common fixes didn't work for my issue - [ ] I have tried removing/destroying my container, and re-creating a new container - [ ] I have tried fresh volume data by backing up and moving/removing the old volume data - [ ] I have tried running the stock `docker run` example(s) in the readme (removing any customizations I added) - [ ] I have tried a newer or older version of Docker Pi-hole (depending what version the issue started in for me) - [ ] I have tried running without my volume data mounts to eliminate volumes as the cause If the above debugging / fixes revealed any new information note it here. Add any other debugging steps you've taken or theories on root cause that may help. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: Questions and Configurations url: https://discourse.pi-hole.net about: Ask a question or get help with configurations. - name: Feature Requests url: https://discourse.pi-hole.net/c/feature-requests/8 about: See existing Feature Requests and suggest new ones. - name: Documentation url: https://docs.pi-hole.net about: Documentation and guides. ================================================ FILE: .github/PULL_REQUEST_TEMPLATE.md ================================================ `{Please select 'base: development' as target branch above! (you can delete this line)}` ## Description ## Motivation and Context ## How Has This Been Tested? ## Types of changes - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) ## Checklist: - [ ] My code follows the code style of this project. - [ ] My change requires a change to the documentation. - [ ] I have updated the documentation accordingly. ================================================ FILE: .github/dco.yml ================================================ require: members: false ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: # Maintain dependencies for GitHub Actions - package-ecosystem: "github-actions" directories: - "/" schedule: interval: "weekly" day: saturday time: "10:00" target-branch: development - package-ecosystem: "docker" directory: "/src/" schedule: interval: "weekly" day: saturday time: "10:00" target-branch: development - package-ecosystem: pip directory: "/test" schedule: interval: weekly day: saturday time: "10:00" open-pull-requests-limit: 10 target-branch: development ================================================ FILE: .github/release.yml ================================================ changelog: exclude: labels: - internal - dependencies authors: - dependabot - github-actions ================================================ FILE: .github/workflows/build-and-publish.yml ================================================ name: Build Image and Publish on: schedule: - cron: "0 5 * * *" push: branches: - development release: types: [published] permissions: contents: read packages: write env: dockerhub: ${{ secrets.DOCKERHUB_NAMESPACE }}/pihole ghcr: ghcr.io/${{ github.repository_owner }}/pihole components_branch: ${{ github.event_name == 'release' && 'master' || 'development' }} jobs: build: runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/386 runner: ubuntu-latest - platform: linux/arm/v6 runner: ubuntu-24.04-arm - platform: linux/arm/v7 runner: ubuntu-24.04-arm - platform: linux/arm64 runner: ubuntu-24.04-arm - platform: linux/riscv64 runner: ubuntu-24.04-arm steps: - name: Prepare name for digest up/download run: | platform=${{ matrix.platform }} echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV - &checkout-repo name: Checkout Repo if: github.event_name != 'schedule' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - &checkout-dev name: Checkout dev branch if scheduled if: github.event_name == 'schedule' uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 with: ref: development - &docker-meta name: Docker meta id: meta uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 #v5.10.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} images: | ${{ env.dockerhub }} ${{ env.ghcr }} flavor: | latest=${{ startsWith(github.ref, 'refs/tags/') }} tags: | type=schedule,pattern=nightly type=raw,value=nightly,enable=${{ github.event_name == 'push' }} type=ref,event=tag - &login-dockerhub name: Login to Docker Hub uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0 with: registry: docker.io username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASS }} - &login-ghcr name: Login to GitHub Container Registry uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0 with: platforms: ${{ matrix.platform}} - &setup-buildx name: Set up Docker Buildx uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f #v3.12.0 with: # Buildx version 0.31.1 broke our publish workflow, this need to be revised when 0.32.0 is released # https://github.com/docker/buildx/releases/tag/v0.31.1 version: v0.31.0 - name: Build container and push by digest id: build uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 #v6.19.2 with: context: ./src/ platforms: ${{ matrix.platform }} build-args: | PIHOLE_DOCKER_TAG=${{ steps.meta.outputs.version }} FTL_BRANCH=${{ env.components_branch }} CORE_BRANCH=${{ env.components_branch }} WEB_BRANCH=${{ env.components_branch }} PADD_BRANCH=${{ env.components_branch }} labels: ${{ steps.meta.outputs.labels }} outputs: | type=image,name=${{ env.dockerhub }},push-by-digest=true,name-canonical=true,push=true - name: Export digests run: | mkdir -p /tmp/digests digest_docker="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest_docker#sha256:}" - name: Upload digest uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0 with: name: digests-${{ env.PLATFORM_PAIR }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 # Merge all the digests into a single file # If we would push immediately above, the individual runners would overwrite each other's images # https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners merge-and-deploy: runs-on: ubuntu-latest needs: - build steps: - *checkout-repo - *checkout-dev - name: Download digests uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0 with: path: /tmp/digests pattern: digests-* merge-multiple: true - *setup-buildx - *docker-meta - *login-dockerhub - *login-ghcr - name: Create manifest list and push (DockerHub and GitHub Container Registry) working-directory: /tmp/digests run: | docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.dockerhub }}@sha256:%s ' *) docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ $(printf '${{ env.ghcr }}@sha256:%s ' *) - name: Inspect images run: | docker buildx imagetools inspect ${{ env.dockerhub }}:${{ steps.meta.outputs.version }} docker buildx imagetools inspect ${{ env.ghcr }}:${{ steps.meta.outputs.version }} ================================================ FILE: .github/workflows/build-and-test.yml ================================================ name: Build Image and Test on: pull_request: permissions: contents: read jobs: build-and-test: runs-on: ${{ matrix.runner }} strategy: fail-fast: false matrix: include: - platform: linux/amd64 runner: ubuntu-latest - platform: linux/386 runner: ubuntu-latest - platform: linux/arm/v6 runner: ubuntu-24.04-arm - platform: linux/arm/v7 runner: ubuntu-24.04-arm - platform: linux/arm64 runner: ubuntu-24.04-arm - platform: linux/riscv64 runner: ubuntu-24.04-arm env: CI_ARCH: ${{ matrix.platform }} steps: - name: Checkout Repo uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Set up QEMU uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0 - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0 with: python-version: "3.13" - name: Run black formatter run: | pip install black black --check --diff test/tests/ - name: Install wheel run: pip install wheel - name: Install dependencies run: pip install -r test/requirements.txt - name: Test with tox run: | CIPLATFORM=${{ env.CI_ARCH }} tox -c test/tox.ini ================================================ FILE: .github/workflows/codespell.yml ================================================ name: Codespell on: pull_request: types: [opened, synchronize, reopened, ready_for_review] permissions: contents: read jobs: spell-check: if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Spell-Checking uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 #v2.2 with: ignore_words_file: .codespellignore ================================================ FILE: .github/workflows/dockerhub-description.yml ================================================ name: Update Docker Hub Description permissions: contents: read on: push: branches: - master paths: - README.md - .github/workflows/dockerhub-description.yml jobs: dockerHubDescription: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Docker Hub Description uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa #v5 with: username: ${{ secrets.DOCKERHUB_USER }} password: ${{ secrets.DOCKERHUB_PASS }} repository: pihole/pihole short-description: ${{ github.event.repository.description }} ================================================ FILE: .github/workflows/editorconfig.yml ================================================ name: Editorconfig-Checker on: pull_request: types: [opened, synchronize, reopened, ready_for_review] permissions: contents: read jobs: editorconfig-checker: if: github.event.pull_request.draft == false runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Get editorconfig-checker uses: editorconfig-checker/action-editorconfig-checker@4b6cd6190d435e7e084fb35e36a096e98506f7b9 #v2.1.0 - name: Run editorconfig-checker run: editorconfig-checker ================================================ FILE: .github/workflows/housekeeping.yml ================================================ name: Remove untagged images from registry on: workflow_dispatch: schedule: - cron: "0 0 * * *" permissions: packages: write jobs: housekeeping: runs-on: ubuntu-latest steps: - name: Delete all containers from repository without tags uses: Chizkiyahu/delete-untagged-ghcr-action@68758dd8caf1d9dbaed1fe9cc1a1f8fcea1c4cf0 #v6.1.0 with: token: ${{ secrets.PAT_TOKEN }} repository_owner: ${{ github.repository_owner }} repository: ${{ github.repository }} untagged_only: true owner_type: org # or user except_untagged_multiplatform: true ================================================ FILE: .github/workflows/merge-conflict.yml ================================================ name: "Check for merge conflicts" on: # So that PRs touching the same files as the push are updated push: # So that the `dirtyLabel` is removed if conflicts are resolve # We recommend `pull_request_target` so that github secrets are available. # In `pull_request` we wouldn't be able to change labels of fork PRs pull_request_target: types: [synchronize] permissions: contents: read pull-requests: write jobs: main: runs-on: ubuntu-latest steps: - name: Check if PRs are have merge conflicts uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 #v3.0.3 with: dirtyLabel: "Merge Conflict" repoToken: "${{ secrets.GITHUB_TOKEN }}" commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request." commentOnClean: "Conflicts have been resolved." ================================================ FILE: .github/workflows/stale.yml ================================================ name: Mark stale issues on: schedule: - cron: '0 8 * * *' workflow_dispatch: issue_comment: permissions: issues: write pull-requests: write env: stale_label: stale jobs: stale_action: if: github.event_name != 'issue_comment' runs-on: ubuntu-latest permissions: issues: write steps: - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 30 days-before-close: 5 stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.' stale-issue-label: '${{ env.stale_label }}' exempt-issue-labels: 'pinned, Fixed in next release, bug, never-stale, documentation, investigating, v6' exempt-all-issue-assignees: true operations-per-run: 300 close-issue-reason: 'not_planned' remove_stale: # trigger "stale" removal immediately when stale issues are commented on # we need to explicitly check that the trigger does not run on comment on a PR as # 'issue_comment' triggers on issues AND PR comments # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only if: ${{ !github.event.issue.pull_request && github.event_name != 'schedule' }} permissions: contents: read # for actions/checkout issues: write # to edit issues label runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Remove 'stale' label run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .github/workflows/stale_pr.yml ================================================ name: Close stale PR # This action will add a `stale` label and close immediately every PR that meets the following conditions: # - it is already marked with "merge conflict" label # - there was no update/comment on the PR in the last 30 days. on: schedule: - cron: '0 10 * * *' workflow_dispatch: jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1 with: repo-token: ${{ secrets.GITHUB_TOKEN }} # Do not automatically mark PR/issue as stale days-before-stale: -1 # Override 'days-before-stale' for PR only days-before-pr-stale: 30 # Close PRs immediately, after marking them 'stale' days-before-pr-close: 0 # only run the action on merge conflict PR any-of-labels: 'Merge Conflict' exempt-pr-labels: 'internal,never-stale,ON HOLD,in progress' exempt-all-pr-assignees: true operations-per-run: 300 stale-pr-message: '' close-pr-message: 'Existing merge conflicts have not been addressed. This PR is considered abandoned.' ================================================ FILE: .github/workflows/sync-back-to-dev.yml ================================================ name: Sync Back to Development on: push: branches: - master permissions: contents: write pull-requests: write jobs: sync-branches: runs-on: ubuntu-latest name: Syncing branches steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2 - name: Opening pull request run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ *.sw* *.pyc .cache __pycache__ .tox .pipenv .eggs UNKNOWN.egg-info .env ci-workspace .gh-workspace docker-compose.yml etc-dnsmasq.d/ etc-pihole/ var-log/ .vscode/ .pytest_cache/ # WIP/test stuff doco.yml # Ignore FTL Binary if it exists src/pihole-FTL ================================================ FILE: .gitmodules ================================================ ================================================ FILE: CHANGELOG.md ================================================ # Docker Pi-Hole changelog Notes about releases will be documented on [docker-pi-hole's github releases page](https://github.com/pi-hole/docker-pi-hole/releases). Breaking changes will be copied to the top of the docker repo's README file to assist with common upgrade issues. See the [Pi-hole releases](https://github.com/pi-hole/pi-hole/releases) for details on updates unrelated to docker image releases ================================================ FILE: CONTRIBUTING.md ================================================ # Pull Request Guidelines Please review the following before opening a pull request (PR) to help your PR go smoothly: * Code changes go to the `development` branch first * To ensure proper testing and quality control, target any code change pull requests against `development` branch. * Make sure the tests pass * Take a look at [TESTING.md](test/TESTING.md) to see how to run tests locally so you do not have to push all your code to a PR and have GitHub Actions run it. * Your tests will probably run faster locally and you get a faster feedback loop. ================================================ FILE: LICENSE ================================================ Copyright (C) 2017 Pi-hole, LLC (https://pi-hole.net) Pi-hole Core This software is licensed under the European Union Public License (EUPL) The license is available in the 22 official languages of the EU. The English version is included here. Please see https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official translations of the other languages. This license applies to the whole project EXCEPT: - any commits made to the master branch prior to the release of version 3.0 The licenses that existed prior to this change have remained intact. ------------------------------------------------------------- EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016 This European Union Public Licence (the EUPL) applies to the Work (as defined below) which is provided under the terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such use is covered by a right of the copyright holder of the Work). The Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following notice immediately following the copyright notice for the Work: Licensed under the EUPL or has expressed by any other means his willingness to license under the EUPL. 1. Definitions In this Licence, the following terms have the following meaning: - The Licence: this Licence. - The Original Work: the work or software distributed or communicated by the Licensor under this Licence, available as Source Code and also as Executable Code as the case may be. - Derivative Works: the works or software that could be created by the Licensee, based upon the Original Work or modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in the country mentioned in Article 15. - The Work: the Original Work or its Derivative Works. - The Source Code: the human-readable form of the Work which is the most convenient for people to study and modify. - The Executable Code: any code which has generally been compiled and which is meant to be interpreted by a computer as a program. - The Licensor: the natural or legal person that distributes or communicates the Work under the Licence. - Contributor(s): any natural or legal person who modifies the Work under the Licence, or otherwise contributes to the creation of a Derivative Work. - The Licensee or You: any natural or legal person who makes any usage of the Work under the terms of the Licence. - Distribution or Communication: any act of selling, giving, lending, renting, distributing, communicating, transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential functionalities at the disposal of any other natural or legal person. 2. Scope of the rights granted by the Licence The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for the duration of copyright vested in the Original Work: - use the Work in any circumstance and for all usage, - reproduce the Work, - modify the Work, and make Derivative Works based upon the Work, - communicate to the public, including the right to make available or display the Work or copies thereof to the public and perform publicly, as the case may be, the Work, - distribute the Work or copies thereof, - lend and rent the Work or copies thereof, - sublicense rights in the Work or copies thereof. Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the applicable law permits so. In the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed by law in order to make effective the licence of the economic rights here above listed. The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the extent necessary to make use of the rights granted on the Work under this Licence. 3. Communication of the Source Code The Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to distribute or communicate the Work. 4. Limitations on copyright Nothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations thereto. 5. Obligations of the Licensee The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those obligations are the following: Attribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work to carry prominent notices stating that the Work has been modified and the date of modification. Copyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless the Original Work is expressly distributed only under this version of the Licence - for example by communicating EUPL v. 1.2 only. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the Work or Derivative Work that alter or restrict the terms of the Licence. Compatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done under the terms of this Compatible Licence. For the sake of this clause, Compatible Licence refers to the licences listed in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail. Provision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available for as long as the Licensee continues to distribute or communicate the Work. Legal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the copyright notice. 6. Chain of Authorship The original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence. Each Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence. Each time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions to the Work, under the terms of this Licence. 7. Disclaimer of Warranty The Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work and may therefore contain defects or bugs inherent to this type of development. For the above reason, the Work is provided under the Licence on an as is basis and without warranties of any kind concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this Licence. This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work. 8. Disclaimer of Liability Except in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, the Licensor will be liable under statutory product liability laws as far such laws apply to the Work. 9. Additional agreements While distributing the Work, You may choose to conclude an additional agreement, defining obligations or services consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by the fact You have accepted any warranty or additional liability. 10. Acceptance of the Licence The provisions of this Licence can be accepted by clicking on an icon I agree placed under the bottom of a window displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms and conditions. Similarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution or Communication by You of the Work or copies thereof. 11. Information to the public In case of any Distribution or Communication of the Work by means of electronic communication by You (for example, by offering to download the Work from a remote location) the distribution channel or media (for example, a website) must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence and the way it may be accessible, concluded, stored and reproduced by the Licensee. 12. Termination of the Licence The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms of the Licence. Such a termination will not terminate the licences of any person who has received the Work from the Licensee under the Licence, provided such persons remain in full compliance with the Licence. 13. Miscellaneous Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work. If any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid and enforceable. The European Commission may publish other linguistic versions or new versions of this Licence or updated versions of the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. New versions of the Licence will be published with a unique version number. All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take advantage of the linguistic version of their choice. 14. Jurisdiction Without prejudice to specific agreement between parties, - any litigation resulting from the interpretation of this License, arising between the European Union institutions, bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union, - any litigation arising between other parties and resulting from the interpretation of this License, will be subject to the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business. 15. Applicable Law Without prejudice to specific agreement between parties, - this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, resides or has his registered office, - this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside a European Union Member State. === Appendix Compatible Licences according to Article 5 EUPL are: - GNU General Public License (GPL) v. 2, v. 3 - GNU Affero General Public License (AGPL) v. 3 - Open Software License (OSL) v. 2.1, v. 3.0 - Eclipse Public License (EPL) v. 1.0 - CeCILL v. 2.0, v. 2.1 - Mozilla Public Licence (MPL) v. 2 - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software - European Union Public Licence (EUPL) v. 1.1, v. 1.2 - Québec Free and Open-Source Licence - Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+) - The European Commission may update this Appendix to later versions of the above licences without producing a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the covered Source Code from exclusive appropriation. - All other changes or additions to this Appendix require the production of a new EUPL version. ================================================ FILE: README.md ================================================ # Docker Pi-hole [![Build Status](https://github.com/pi-hole/docker-pi-hole/workflows/Build%20Image%20and%20Test/badge.svg)](https://github.com/pi-hole/docker-pi-hole/actions?query=workflow%3A%22Build+Image+and+Test%22) [![Docker Stars](https://img.shields.io/docker/stars/pihole/pihole.svg?maxAge=604800)](https://store.docker.com/community/images/pihole/pihole) [![Docker Pulls](https://img.shields.io/docker/pulls/pihole/pihole.svg?maxAge=604800)](https://store.docker.com/community/images/pihole/pihole)
Pi-hole website
Network-wide ad blocking via your own Linux hardware

Pi-hole website | Documentation | Discourse Forum | Donate


## Upgrade Notes > [!NOTE] > **Using Watchtower?\ > See the [Note on Watchtower](https://docs.pi-hole.net/docker/tips-and-tricks/#note-on-watchtower) in our documentation**. > [!TIP] > Some users [have reported issues](https://github.com/pi-hole/docker-pi-hole/issues/963#issuecomment-1095602502) with using the `--privileged` flag on `2022.04` and above.\ > TL;DR, don't use that mode, and be [explicit with the permitted caps](https://docs.pi-hole.net/docker/#note-on-capabilities) (if needed) instead. ## Quick Start Using [Docker-compose](https://docs.docker.com/compose/install/): 1. Copy the below docker compose example and update as needed: ```yml # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ services: pihole: container_name: pihole image: pihole/pihole:latest ports: # DNS Ports - "53:53/tcp" - "53:53/udp" # Default HTTP Port - "80:80/tcp" # Default HTTPs Port. FTL will generate a self-signed certificate - "443:443/tcp" # Uncomment the line below if you are using Pi-hole as your DHCP server #- "67:67/udp" # Uncomment the line below if you are using Pi-hole as your NTP server #- "123:123/udp" environment: # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g: TZ: 'Europe/London' # Set a password to access the web interface. Not setting one will result in a random password being assigned FTLCONF_webserver_api_password: 'correct horse battery staple' # If using Docker's default `bridge` network setting the dns listening mode should be set to 'ALL' FTLCONF_dns_listeningMode: 'ALL' # Volumes store your data between container upgrades volumes: # For persisting Pi-hole's databases and common configuration file - './etc-pihole:/etc/pihole' # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most starting fresh with Pi-hole v6. If you're upgrading from v5 you and have used this directory before, you should keep it enabled for the first v6 container start to allow for a complete migration. It can be removed afterwards. Needs environment variable FTLCONF_misc_etc_dnsmasq_d: 'true' #- './etc-dnsmasq.d:/etc/dnsmasq.d' cap_add: # See https://docs.pi-hole.net/docker/#note-on-capabilities # Required if you are using Pi-hole as your DHCP server, else not needed - NET_ADMIN # Required if you are using Pi-hole as your NTP client to be able to set the host's system time - SYS_TIME # Optional, if Pi-hole should get some more processing time - SYS_NICE restart: unless-stopped ``` 2. Run `docker compose up -d` to build and start pi-hole (Syntax may be `docker-compose` on older systems). > [!NOTE] > Volumes are recommended for persisting data across container re-creations for updating images. ### Automatic Ad List Updates `cron` is baked into the container and will grab the newest versions of your lists and flush your logs. This happens once per week in the small hours of Sunday morning. ## Documentation For more detailed information, please refer to our documentation: - [Running DHCP from Docker Pi-Hole](https://docs.pi-hole.net/docker/DHCP/) - [Configuration](https://docs.pi-hole.net/docker/configuration/) - [Tips and Tricks](https://docs.pi-hole.net/docker/tips-and-tricks/) - [Docker tags and versioning](https://docs.pi-hole.net/docker/#docker-tags-and-versioning) - [Upgrading, Persistence, and Customizations](https://docs.pi-hole.net/docker/upgrading/) ## Docker tags and versioning The primary docker tags are explained in the following table. [Click here to see the full list of tags](https://hub.docker.com/r/pihole/pihole/tags). See [GitHub Release notes](https://github.com/pi-hole/docker-pi-hole/releases) to see the specific version of Pi-hole Core, Web, and FTL included in the release. The Date-based (including incremented "Patch" versions) do not relate to any kind of semantic version number, rather a date is used to differentiate between the new version and the old version, nothing more. Release notes will always contain full details of changes in the container, including changes to core Pi-hole components. | tag | description | | :--- | :--- | | `latest` | Always the latest release | | `2022.04.0` | Date-based release | | `nightly` | Built and pushed whenever there are changes on the `development` branch and additionally produced by the scheduled nightly job. These are the most experimental development images and may change frequently | ## User Feedback Please report issues on the [GitHub project](https://github.com/pi-hole/docker-pi-hole) when you suspect something docker related. Pi-hole or general docker questions are best answered on our [user forums](https://discourse.pi-hole.net/c/bugs-problems-issues/docker/30) ================================================ FILE: build.sh ================================================ #!/bin/bash # Usage function usage() { echo "Usage: $0 [-l] [-f ] [-c ] [-w ] [-t ] [use_cache]" echo "Options:" echo " -f, --ftlbranch Specify FTL branch (cannot be used in conjunction with -l)" echo " -c, --corebranch Specify Core branch" echo " -w, --webbranch Specify Web branch" echo " -p, --paddbranch Specify PADD branch" echo " -t, --tag Specify Docker image tag (default: pihole:local)" echo " -l, --local Use locally built FTL binary (requires src/pihole-FTL file)" echo " use_cache Enable caching (by default --no-cache is used)" echo "" echo "If no options are specified, the following command will be executed:" echo " docker buildx build src/. --tag pihole:local --load --no-cache" exit 1 } # Set default values TAG="pihole:local" DOCKER_BUILD_CMD="docker buildx build src/. --tag ${TAG} --load --no-cache" FTL_FLAG=false CORE_FORK="pi-hole" WEB_FORK="pi-hole" PADD_FORK="pi-hole" # Check if buildx is installed docker buildx version >/dev/null 2>&1 if [ $? -ne 0 ]; then echo "Error: Docker buildx is required to build this image. For installation instructions, see:" echo " https://github.com/docker/buildx#installing" exit 1 fi # Function to check if a custom branch entered by the user is valid check_branch_exists() { local repo=$1 local branch=$2 local fork=$3 local url if [ "$repo" == "ftl" ]; then # Special case for FTL - we check for the binary instead of just the branch - in case it is not yet built. url="https://ftl.pi-hole.net/${branch}/pihole-FTL-amd64" else url="https://github.com/${fork}/${repo}/blob/${branch}/README.md" fi local http_code http_code=$(curl -sI "$url" -o /dev/null -w "%{http_code}") if [ "${http_code}" -ne 200 ]; then echo "Error: $repo branch '$branch' not found. Exiting." exit 1 fi } # Parse command line arguments while [[ $# -gt 0 ]]; do key="$1" case $key in -l | --local) if [ ! -f "src/pihole-FTL" ]; then echo "File 'src/pihole-FTL' not found. Exiting." exit 1 fi if [ "$FTL_FLAG" = true ]; then echo "Error: Both -l and -f cannot be used together." usage fi FTL_FLAG=true DOCKER_BUILD_CMD+=" --build-arg FTL_SOURCE=local" shift ;; -f | --ftlbranch) if [ "$FTL_FLAG" = true ]; then echo "Error: Both -l and -f cannot be used together." usage fi FTL_FLAG=true FTL_BRANCH="$2" check_branch_exists "ftl" "$FTL_BRANCH" DOCKER_BUILD_CMD+=" --build-arg FTL_BRANCH=$FTL_BRANCH" shift shift ;; -c | --corebranch) CORE_BRANCH="$2" check_branch_exists "pi-hole" "$CORE_BRANCH" "$CORE_FORK" DOCKER_BUILD_CMD+=" --build-arg CORE_BRANCH=$CORE_BRANCH" shift shift ;; -w | --webbranch) WEB_BRANCH="$2" check_branch_exists "web" "$WEB_BRANCH" "$WEB_FORK" DOCKER_BUILD_CMD+=" --build-arg WEB_BRANCH=$WEB_BRANCH" shift shift ;; -p | --paddbranch) PADD_BRANCH="$2" check_branch_exists "padd" "$PADD_BRANCH" DOCKER_BUILD_CMD+=" --build-arg PADD_BRANCH=$PADD_BRANCH" shift shift ;; -cf | --corefork) CORE_FORK="$2" DOCKER_BUILD_CMD+=" --build-arg CORE_FORK=$CORE_FORK" shift shift ;; -wf | --webfork) WEB_FORK="$2" DOCKER_BUILD_CMD+=" --build-arg WEB_FORK=$WEB_FORK" shift shift ;; -pf | --paddfork) PADD_FORK="$2" DOCKER_BUILD_CMD+=" --build-arg PADD_FORK=$PADD_FORK" shift shift ;; -t | --tag) CUSTOM_TAG="$2" DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/$TAG/$CUSTOM_TAG} TAG=$CUSTOM_TAG shift shift ;; use_cache) DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/--no-cache/} shift ;; *) echo "Unknown option: $1" usage ;; esac done # Execute the docker build command echo "Executing command: $DOCKER_BUILD_CMD" eval "${DOCKER_BUILD_CMD}" # Check exit code of previous command if [ $? -ne 0 ]; then echo "" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" echo "!! ERROR: Docker build failed, please review logs above !!" echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" exit 1 else echo "" echo "Successfully built Docker image with tag '$TAG'" docker images "${TAG}" fi ================================================ FILE: examples/Caddyfile ================================================ pihole-dev.lab { tls internal redir / /admin reverse_proxy pihole:8081 } ================================================ FILE: examples/docker-compose-caddy-proxy.yml ================================================ services: # Caddy example derived from Caddy's own example at https://hub.docker.com/_/caddy caddy: container_name: caddy image: caddy:latest networks: - caddy-net # Network exclusively for Caddy-proxied containers restart: unless-stopped ports: - "80:80" - "443:443" - "443:443/udp" # QUIC protocol support: https://www.chromium.org/quic/ volumes: - ./Caddyfile:/etc/caddy/Caddyfile # config file on host in same directory as docker-compose.yml for easy editing. #- $PWD/site:/srv # Only use if you are serving a website behind caddy - caddy_data:/data # Use docker volumes here bc no need to access these files from host - caddy_config:/config # Use docker volumes here bc no need to access these files from host # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/ pihole: depends_on: - caddy container_name: pihole image: pihole/pihole:latest ports: - "8081:80/tcp" # Pi-hole web admin interface, proxied through Caddy (configure port in Caddyfile) # Following are NOT proxied through Caddy, bound to host net instead: - "53:53/udp" - "53:53/tcp" #- "67:67/udp" # DHCP, if desired. If not bound to host net you need an mDNS proxy service configured somewhere on host net. # ref: https://docs.pi-hole.net/docker/DHCP/ environment: # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g: TZ: 'Europe/London' # Set a password to access the web interface. Not setting one will result in a random password being assigned FTLCONF_webserver_api_password: 'correct horse battery staple' # Volumes store your data between container upgrades volumes: # For persisting Pi-hole's databases and common configuration file - './etc-pihole:/etc/pihole' # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most. #- './etc-dnsmasq.d:/etc/dnsmasq.d' cap_add: # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities # Required if you are using Pi-hole as your DHCP server, else not needed - NET_ADMIN restart: unless-stopped # ref: https://hub.docker.com/_/caddy networks: caddy-net: driver: bridge name: caddy-net # ref: https://hub.docker.com/_/caddy volumes: caddy_data: external: true # May need to create volume with 'docker volume create caddy_data' caddy_config: ================================================ FILE: src/.dockerignore ================================================ **/*.sw* .tox .git **/__pycache__ .pipenv ================================================ FILE: src/Dockerfile ================================================ # syntax=docker/dockerfile:1 ARG FTL_SOURCE=remote # Pull Stable images FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS base ARG TARGETPLATFORM ARG WEB_BRANCH="development" ARG CORE_BRANCH="development" ARG FTL_BRANCH="development" ARG PIHOLE_DOCKER_TAG="dev-localbuild" ARG PADD_BRANCH="development" ARG CORE_FORK="pi-hole" ARG WEB_FORK="pi-hole" ARG PADD_FORK="pi-hole" ARG PIHOLE_UID=1000 ARG PIHOLE_GID=1000 ENV DNSMASQ_USER=pihole ENV FTL_CMD=no-daemon RUN apk add --no-cache \ bash \ bash-completion \ bind-tools \ binutils \ coreutils \ curl \ git \ # Install grep to avoid issues in pihole -w/b with the default busybox grep grep \ iproute2 \ jq \ libcap \ logrotate \ ncurses \ procps-ng \ psmisc \ shadow \ sudo \ tzdata \ unzip \ wget # For nightly images, we install gdb and screen for ease of debugging (this is # not included in the default image to keep it small), and also prepare the # system for a core dump. Furthermore, we already add the required signal # instructions to the gdb config file RUN if [ "${PIHOLE_DOCKER_TAG}" = "nightly" ]; then \ apk add --no-cache gdb screen && \ echo "ulimit -c unlimited" >> /etc/profile && \ echo "handle SIGHUP nostop SIGPIPE nostop SIGTERM nostop SIG32 nostop SIG33 nostop SIG34 nostop SIG35 nostop SIG36 nostop SIG37 nostop SIG38 nostop SIG39 nostop SIG40 nostop SIG41 nostop" > /root/.gdbinit; \ fi ADD https://ftl.pi-hole.net/macvendor.db /macvendor.db COPY crontab.txt /crontab.txt # Add PADD to the container, too. ADD --chmod=0755 https://raw.githubusercontent.com/${PADD_FORK}/PADD/${PADD_BRANCH}/padd.sh /usr/local/bin/padd # download a the main repos from github # if the branch is master we clone the latest tag as sometimes the master branch contains meta changes that have not been tagged # (we need to create a new "master" branch to avoid the "detached HEAD" state for the version check to work correctly) RUN clone_repo() { \ FORK="$1"; \ REPO="$2"; \ BRANCH="$3"; \ DEST="$4"; \ CLONE_BRANCH="$BRANCH"; \ if [ "$BRANCH" = "master" ]; then \ CLONE_BRANCH=$(curl -s https://api.github.com/repos/${FORK}/${REPO}/releases/latest | jq -r .tag_name); \ fi; \ git clone --branch "$CLONE_BRANCH" --single-branch --depth 1 "https://github.com/${FORK}/${REPO}.git" "$DEST"; \ cd "$DEST"; \ if [ "$BRANCH" = "master" ]; then git checkout -b master; fi; \ }; \ clone_repo "${WEB_FORK}" "web" "${WEB_BRANCH}" "/var/www/html/admin"; \ clone_repo "${CORE_FORK}" "pi-hole" "${CORE_BRANCH}" "/etc/.pihole" RUN cd /etc/.pihole && \ install -Dm755 -d /opt/pihole && \ install -Dm755 -t /opt/pihole gravity.sh && \ install -Dm755 -t /opt/pihole ./advanced/Scripts/*.sh && \ install -Dm755 -t /opt/pihole ./advanced/Scripts/COL_TABLE && \ install -Dm755 -d /etc/pihole && \ install -Dm644 -t /etc/pihole ./advanced/Templates/logrotate && \ install -Dm755 -d /var/log/pihole && \ install -Dm755 -d /var/lib/logrotate && \ install -Dm755 -t /usr/local/bin pihole && \ install -Dm644 ./advanced/bash-completion/pihole.bash /etc/bash_completion.d/pihole && \ install -Dm644 ./advanced/bash-completion/pihole-ftl.bash /etc/bash_completion.d/pihole-FTL && \ install -T -m 0755 ./advanced/Templates/pihole-FTL-prestart.sh /opt/pihole/pihole-FTL-prestart.sh && \ install -T -m 0755 ./advanced/Templates/pihole-FTL-poststop.sh /opt/pihole/pihole-FTL-poststop.sh && \ addgroup -S pihole -g ${PIHOLE_GID} && adduser -S pihole -G pihole -u ${PIHOLE_UID} && \ echo "${PIHOLE_DOCKER_TAG}" > /pihole.docker.tag COPY --chmod=0755 bash_functions.sh /usr/bin/bash_functions.sh COPY --chmod=0755 start.sh /usr/bin/start.sh EXPOSE 53 53/udp EXPOSE 67/udp EXPOSE 80 EXPOSE 123/udp EXPOSE 443 ## Buildkit can do some fancy stuff and we can use it to either download FTL from ftl.pi-hole.net or use a local copy FROM base AS remote-ftl-install # Default stage if FTL_SOURCE is not explicitly set to "local" # Download the latest version of pihole-FTL for the correct architecture RUN if [ "$TARGETPLATFORM" = "linux/amd64" ]; then FTLARCH=amd64; \ elif [ "$TARGETPLATFORM" = "linux/386" ]; then FTLARCH=386; \ elif [ "$TARGETPLATFORM" = "linux/arm/v6" ]; then FTLARCH=armv6; \ elif [ "$TARGETPLATFORM" = "linux/arm/v7" ]; then FTLARCH=armv7; \ # Note for the future, "linux/arm6/v8" is not a valid value for TARGETPLATFORM, despite the CI platform name being that. elif [ "$TARGETPLATFORM" = "linux/arm64" ]; then FTLARCH=arm64; \ elif [ "$TARGETPLATFORM" = "linux/riscv64" ]; then FTLARCH=riscv64; \ else FTLARCH=amd64; fi \ && echo "Arch: ${TARGETPLATFORM}, FTLARCH: ${FTLARCH}" \ && if [ "${FTL_BRANCH}" = "master" ]; then URL="https://github.com/pi-hole/ftl/releases/latest/download"; else URL="https://ftl.pi-hole.net/${FTL_BRANCH}"; fi \ && curl -sSL "${URL}/pihole-FTL-${FTLARCH}" -o /usr/bin/pihole-FTL \ && chmod +x /usr/bin/pihole-FTL \ && readelf -h /usr/bin/pihole-FTL || (echo "Error with downloaded FTL binary" && exit 1) \ && /usr/bin/pihole-FTL -vv FROM base AS local-ftl-install # pihole-FTL must be built from source and copied to the src directory first! COPY --chmod=0755 pihole-FTL /usr/bin/pihole-FTL RUN readelf -h /usr/bin/pihole-FTL || (echo "Error with local FTL binary" && exit 1) # Use the appropriate FTL Install stage based on the FTL_SOURCE build-arg FROM ${FTL_SOURCE}-ftl-install AS final HEALTHCHECK CMD dig -p $(pihole-FTL --config dns.port) +short +norecurse +retry=0 @127.0.0.1 pi.hole || exit 1 ENTRYPOINT ["start.sh"] ================================================ FILE: src/bash_functions.sh ================================================ #!/bin/bash # Some of the bash_functions use utilities from Pi-hole's utils.sh # shellcheck disable=SC2154 # shellcheck source=/dev/null # . /opt/pihole/utils.sh ####################### # returns value from FTLs config file using pihole-FTL --config # # Takes one argument: key # Example getFTLConfigValue dns.piholePTR ####################### getFTLConfigValue() { pihole-FTL --config -q "${1}" } ####################### # sets value in FTLs config file using pihole-FTL --config # # Takes two arguments: key and value # Example setFTLConfigValue dns.piholePTR PI.HOLE # # Note, for complex values such as dns.upstreams, you should wrap the value in single quotes: # setFTLConfigValue dns.upstreams '[ "8.8.8.8" , "8.8.4.4" ]' ####################### setFTLConfigValue() { pihole-FTL --config "${1}" "${2}" >/dev/null } set_uid_gid() { echo " [i] Setting up user & group for the pihole user" currentUid=$(id -u pihole) # If PIHOLE_UID is set, modify the pihole group's id to match if [ -n "${PIHOLE_UID}" ]; then if [[ ${currentUid} -ne ${PIHOLE_UID} ]]; then echo " [i] Changing ID for user: pihole (${currentUid} => ${PIHOLE_UID})" usermod -o -u "${PIHOLE_UID}" pihole else echo " [i] ID for user pihole is already ${PIHOLE_UID}, no need to change" fi else echo " [i] PIHOLE_UID not set in environment, using default (${currentUid})" fi currentGid=$(id -g pihole) # If PIHOLE_GID is set, modify the pihole group's id to match if [ -n "${PIHOLE_GID}" ]; then if [[ ${currentGid} -ne ${PIHOLE_GID} ]]; then echo " [i] Changing ID for group: pihole (${currentGid} => ${PIHOLE_GID})" groupmod -o -g "${PIHOLE_GID}" pihole else echo " [i] ID for group pihole is already ${PIHOLE_GID}, no need to change" fi else echo " [i] PIHOLE_GID not set in environment, using default (${currentGid})" fi echo "" } install_additional_packages() { if [ -n "${ADDITIONAL_PACKAGES}" ]; then echo " [i] Additional packages requested: ${ADDITIONAL_PACKAGES}" echo " [i] Fetching APK repository metadata." if ! apk update; then echo " [i] Failed to fetch APK repository metadata." else echo " [i] Installing additional packages: ${ADDITIONAL_PACKAGES}." # shellcheck disable=SC2086 if ! apk add --no-cache ${ADDITIONAL_PACKAGES}; then echo " [i] Failed to install additional packages." fi fi echo "" fi } start_cron() { echo " [i] Starting crond for scheduled scripts. Randomizing times for gravity and update checker" # Randomize gravity update time sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /crontab.txt # Randomize update checker time sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /crontab.txt /usr/bin/crontab /crontab.txt /usr/sbin/crond echo "" } install_logrotate() { # Install the logrotate config file - this is done already in Dockerfile # but if a user has mounted a volume over /etc/pihole, it will have been lost # pihole-FTL-prestart.sh will set the ownership of the file to root:root echo " [i] Ensuring logrotate script exists in /etc/pihole" install -Dm644 -t /etc/pihole /etc/.pihole/advanced/Templates/logrotate echo "" } migrate_gravity() { echo " [i] Gravity migration checks" gravityDBfile=$(getFTLConfigValue files.gravity) if [[ ! -f /etc/pihole/adlists.list ]]; then echo " [i] No adlist file found, creating one with a default blocklist" echo "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" >/etc/pihole/adlists.list fi if [ ! -f "${gravityDBfile}" ]; then echo " [i] ${gravityDBfile} does not exist (Likely due to a fresh volume). This is a required file for Pi-hole to operate." echo " [i] Gravity will now be run to create the database" pihole -g else echo " [i] Existing gravity database found - schema will be upgraded if necessary" # source the migration script and run the upgrade function source /etc/.pihole/advanced/Scripts/database_migration/gravity-db.sh local upgradeOutput upgradeOutput=$(upgrade_gravityDB "${gravityDBfile}" "/etc/pihole") printf "%b" "${upgradeOutput}\\n" | sed 's/^/ /' fi echo "" } # shellcheck disable=SC2034 ftl_config() { # Force a check of pihole-FTL --config, this will read any environment variables and set them in the config file # suppress the output as we don't need to see the default values. getFTLConfigValue >/dev/null # If FTLCONF_files_macvendor is not set if [[ -z "${FTLCONF_files_macvendor:-}" ]]; then # User is not passing in a custom location - so force FTL to use the file we moved to / during the build setFTLConfigValue "files.macvendor" "/macvendor.db" chown pihole:pihole /macvendor.db fi # If getFTLConfigValue "dns.upstreams" returns [], default to Google's DNS server if [[ $(getFTLConfigValue "dns.upstreams") == "[]" ]]; then echo " [i] No DNS upstream set in environment or config file, defaulting to Google DNS" setFTLConfigValue "dns.upstreams" "[\"8.8.8.8\", \"8.8.4.4\"]" fi setup_web_password } migrate_v5_configs() { # Previously, Pi-hole created a number of files in /etc/dnsmasq.d # During migration, their content is copied into the new single source of # truth file /etc/pihole/pihole.toml and the old files are moved away to # avoid conflicts with other services on this system echo " [i] Migrating dnsmasq configuration files" V6_CONF_MIGRATION_DIR="/etc/pihole/migration_backup_v6" # Create target directory and make it owned by pihole:pihole mkdir -p "${V6_CONF_MIGRATION_DIR}" chown pihole:pihole "${V6_CONF_MIGRATION_DIR}" # Move all conf files originally created by Pi-hole into this directory # - 01-pihole.conf # - 02-pihole-dhcp.conf # - 04-pihole-static-dhcp.conf # - 05-pihole-custom-cname.conf # - 06-rfc6761.conf mv /etc/dnsmasq.d/0{1,2,4,5}-pihole*.conf "${V6_CONF_MIGRATION_DIR}/" 2>/dev/null || true mv /etc/dnsmasq.d/06-rfc6761.conf "${V6_CONF_MIGRATION_DIR}/" 2>/dev/null || true echo "" # Finally, after everything is in place, we can create the new config file # /etc/pihole/pihole.toml # This file will be created with the default settings unless the user has # changed settings via setupVars.conf or the other dnsmasq files moved above # During migration, setupVars.conf is moved to /etc/pihole/migration_backup_v6 local FTLoutput FTLoutput=$(pihole-FTL migrate v6) # Print the output of the FTL migration prefacing every line with six # spaces for alignment with other container output. Replace the first line to match the style of the other messages # We suppress the message about environment variables as these will be set on FTL's first real start printf "%b" "${FTLoutput}\\n" | sed 's/^/ /' | sed 's/ Migrating config to Pi-hole v6.0 format/ [i] Migrating config to Pi-hole v6.0 format/' | sed 's/- 0 entries are forced through environment//' # Print a blank line for separation echo "" } setup_web_password() { if [ -z "${FTLCONF_webserver_api_password+x}" ] && [ -n "${WEBPASSWORD_FILE}" ] && [ -r "/run/secrets/${WEBPASSWORD_FILE}" ]; then echo " [i] Setting FTLCONF_webserver_api_password from file" export FTLCONF_webserver_api_password=$(<"/run/secrets/${WEBPASSWORD_FILE}") fi # If FTLCONF_webserver_api_password is not set if [ -z "${FTLCONF_webserver_api_password+x}" ]; then # Is this already set to something other than blank (default) in FTL's config file? (maybe in a volume mount) if [[ $(pihole-FTL --config webserver.api.pwhash) ]]; then echo " [i] Password already set in config file" return else # If we are here, the password is set in neither the environment nor the config file # We will generate a random password. RANDOMPASSWORD=$(tr -dc _A-Z-a-z-0-9 /dev/null pihole-FTL --config webserver.api.password "$RANDOMPASSWORD" >/dev/null # To avoid printing this if conditional in bash debug, turn off debug above.. # then re-enable debug if necessary (more code but cleaner printed output) if [ "${PH_VERBOSE:-0}" -gt 0 ]; then set -x fi fi else echo " [i] Assigning password defined by Environment Variable" fi } fix_capabilities() { # Testing on Docker 20.10.14 with no caps set shows the following caps available to the container: # Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep # FTL can also use CAP_NET_ADMIN and CAP_SYS_NICE. If we try to set them when they haven't been explicitly enabled, FTL will not start. Test for them first: echo " [i] Setting capabilities on pihole-FTL where possible" capsh --has-p=cap_chown 2>/dev/null && CAP_STR+=',CAP_CHOWN' capsh --has-p=cap_net_bind_service 2>/dev/null && CAP_STR+=',CAP_NET_BIND_SERVICE' capsh --has-p=cap_net_raw 2>/dev/null && CAP_STR+=',CAP_NET_RAW' capsh --has-p=cap_net_admin 2>/dev/null && CAP_STR+=',CAP_NET_ADMIN' || DHCP_READY='false' capsh --has-p=cap_sys_nice 2>/dev/null && CAP_STR+=',CAP_SYS_NICE' capsh --has-p=cap_sys_time 2>/dev/null && CAP_STR+=',CAP_SYS_TIME' if [[ ${CAP_STR} ]]; then # We have the (some of) the above caps available to us - apply them to pihole-FTL echo " [i] Applying the following caps to pihole-FTL:" IFS=',' read -ra CAPS <<<"${CAP_STR:1}" for i in "${CAPS[@]}"; do echo " * ${i}" done setcap "${CAP_STR:1}"+ep "$(which pihole-FTL)" || ret=$? if [[ $DHCP_READY == false ]] && [[ $FTLCONF_dhcp_active == true ]]; then # DHCP is requested but NET_ADMIN is not available. echo "ERROR: DHCP requested but NET_ADMIN is not available. DHCP will not be started." echo " Please add cap_net_admin to the container's capabilities or disable DHCP." setFTLConfigValue dhcp.active false fi if [[ $ret -ne 0 && "${DNSMASQ_USER:-pihole}" != "root" ]]; then echo " [!] ERROR: Unable to set capabilities for pihole-FTL. Cannot run as non-root." echo " If you are seeing this error, please set the environment variable 'DNSMASQ_USER' to the value 'root'" exit 1 fi else echo " [!] ERROR: Unable to set capabilities for pihole-FTL." echo " Please ensure that the container has the required capabilities." exit 1 fi echo "" } ================================================ FILE: src/crontab.txt ================================================ 59 1 * * 0 PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updateGravity >/var/log/pihole/pihole_updateGravity.log || cat /var/log/pihole/pihole_updateGravity.log 00 00 * * * PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole flush once quiet 59 17 * * * PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker ================================================ FILE: src/start.sh ================================================ #!/bin/bash if [ ! -x /bin/sh ]; then echo "Executable test for /bin/sh failed. Your Docker version is too old to run Alpine 3.14+ and Pi-hole. You must upgrade Docker."; exit 1; fi if [ "${PH_VERBOSE:-0}" -gt 0 ]; then set -x fi trap stop TERM INT QUIT HUP ERR CAPSH_PID="" TRAP_TRIGGERED=0 start() { # The below functions are all contained in bash_functions.sh # shellcheck source=/dev/null . /usr/bin/bash_functions.sh # If the file /etc/pihole/setupVars.conf exists, but /etc/pihole/pihole.toml does not, then we are migrating v5->v6 # FTL Will handle the migration of the config files if [[ -f /etc/pihole/setupVars.conf && ! -f /etc/pihole/pihole.toml ]]; then echo " [i] v5 files detected that have not yet been migrated to v6" echo "" migrate_v5_configs fi # =========================== # Initial checks # =========================== # If PIHOLE_UID is set, modify the pihole user's id to match set_uid_gid # Configure FTL with any environment variables if needed echo " [i] Starting FTL configuration" ftl_config # Install additional packages inside the container if requested install_additional_packages # Start crond for scheduled scripts (logrotate, pihole flush, gravity update etc) start_cron # Install the logrotate config file install_logrotate #migrate Gravity Database if needed: migrate_gravity echo " [i] pihole-FTL pre-start checks" # Run the post stop script to cleanup any remaining artifacts from a previous run sh /opt/pihole/pihole-FTL-poststop.sh fix_capabilities sh /opt/pihole/pihole-FTL-prestart.sh # Get the FTL log file path from the config FTLlogFile=$(getFTLConfigValue files.log.ftl) # Get the EOF position of the FTL log file so that we can tail from there later. local startFrom startFrom=$(stat -c%s "${FTLlogFile}") echo " [i] Starting pihole-FTL ($FTL_CMD) as ${DNSMASQ_USER}" echo "" capsh --user="${DNSMASQ_USER}" --keep=1 -- -c "/usr/bin/pihole-FTL $FTL_CMD >/dev/null" & # Notes on above: # - DNSMASQ_USER default of pihole is in Dockerfile & can be overwritten by runtime container env # - /var/log/pihole/pihole*.log has FTL's output that no-daemon would normally print in FG too # prevent duplicating it in docker logs by sending to dev null # We need the PID of the capsh process so that we can wait for it to finish CAPSH_PID=$! # Wait for FTL to start by monitoring the FTL log file for the "FTL started" line if ! timeout 30 tail -F -c +$((startFrom + 1)) -- "${FTLlogFile}" | grep -q '########## FTL started'; then echo " [!] ERROR: Did not find 'FTL started' message in ${FTLlogFile} in 30 seconds, stopping container" exit 1 fi pihole updatechecker local versionsOutput versionsOutput=$(pihole -v) echo " [i] Version info:" printf "%b" "${versionsOutput}\\n" | sed 's/^/ /' echo "" if [ "${TAIL_FTL_LOG:-1}" -eq 1 ]; then # Start tailing the FTL log file from the EOF position we recorded on container start tail -F -c +$((startFrom + 1)) -- "${FTLlogFile}" & else echo " [i] FTL log output is disabled. Remove the Environment variable TAIL_FTL_LOG, or set it to 1 to enable FTL log output." fi # Wait for the capsh process (which spawned FTL) to finish wait $CAPSH_PID FTL_EXIT_CODE=$? # If we are here, then FTL has exited. # If the trap was triggered, then stop will have already been called if [ $TRAP_TRIGGERED -eq 0 ]; then # Pass the exit code through to the stop function stop $FTL_EXIT_CODE fi } stop() { local FTL_EXIT_CODE=$1 # if we have nothing in FTL_EXIT_CODE, then have been called by the trap. Close FTL and wait for the CAPSH_PID to finish if [ -z "${FTL_EXIT_CODE}" ]; then TRAP_TRIGGERED=1 echo "" echo " [i] Container stop requested..." echo " [i] pihole-FTL is running - Attempting to shut it down cleanly" echo "" killall --signal 15 pihole-FTL wait $CAPSH_PID FTL_EXIT_CODE=$? fi # Wait for a few seconds to allow the FTL log tail to catch up before exiting the container sleep 2 # ensure the exit code is an integer, if not set it to 1 if ! [[ "${FTL_EXIT_CODE}" =~ ^[0-9]+$ ]]; then FTL_EXIT_CODE=1 fi sh /opt/pihole/pihole-FTL-poststop.sh echo "" echo " [i] pihole-FTL exited with status $FTL_EXIT_CODE" echo "" echo " [i] Container will now stop or restart depending on your restart policy" echo " https://docs.docker.com/engine/containers/start-containers-automatically/#use-a-restart-policy" echo "" exit "${FTL_EXIT_CODE}" } start ================================================ FILE: test/TESTING.md ================================================ # Prerequisites Make sure you have `docker`, `python` and `tox` installed. # Running tests locally `tox -c test/tox.ini` Should result in: - An image named `pihole:CI_container` being built - Tests being ran to confirm the image doesn't have any regressions ================================================ FILE: test/requirements.txt ================================================ pytest == 9.0.2 pytest-testinfra == 10.2.2 pytest-clarity == 1.0.1 tox == 4.35.0 # Not adding pytest-xdist as using pytest with n > 1 cores # causes random issues with the emulated architectures ================================================ FILE: test/tests/__init__.py ================================================ ================================================ FILE: test/tests/conftest.py ================================================ import pytest import subprocess import testinfra import testinfra.backend.docker import os # Monkeypatch sh to bash, if they ever support non hard code /bin/sh this can go away # https://github.com/pytest-dev/pytest-testinfra/blob/master/testinfra/backend/docker.py def run_bash(self, command, *args, **kwargs): cmd = self.get_command(command, *args) if self.user is not None: out = self.run_local( "docker exec -u %s %s /bin/bash -c %s", self.user, self.name, cmd ) else: out = self.run_local("docker exec %s /bin/bash -c %s", self.name, cmd) out.command = self.encode(cmd) return out testinfra.backend.docker.DockerBackend.run = run_bash # scope='session' uses the same container for all the tests; # scope='function' uses a new container per test function. @pytest.fixture(scope="function") def docker(request): # Get platform from environment variable, default to None if not set platform = os.environ.get("CIPLATFORM") # build the docker run command with args cmd = ["docker", "run", "-d", "-t"] # Only add platform flag if CIPLATFORM is set if platform: cmd.extend(["--platform", platform]) # Get env vars from parameterization env_vars = getattr(request, "param", []) if isinstance(env_vars, str): env_vars = [env_vars] # add parameterized environment variables for env_var in env_vars: cmd.extend(["-e", env_var]) # add default TZ if not already set if not any("TZ=" in arg for arg in cmd): cmd.extend(["-e", 'TZ="Europe/London"']) # add the image name cmd.append("pihole:CI_container") # run a container docker_id = subprocess.check_output(cmd).decode().strip() # return a testinfra connection to the container yield testinfra.get_host("docker://" + docker_id) # at the end of the test suite, destroy the container subprocess.check_call(["docker", "rm", "-f", docker_id]) ================================================ FILE: test/tests/test_bash_functions.py ================================================ import pytest # Adding 5 seconds sleep to give the emulated architecture time to run @pytest.mark.parametrize("docker", ["FTLCONF_webserver_port=999"], indirect=True) def test_ftlconf_webserver_port(docker): func = docker.run("echo ${FTLCONF_webserver_port}") assert "999" in func.stdout func = docker.run(""" sleep 5 pihole-FTL --config webserver.port """) assert "999" in func.stdout # Adding 5 seconds sleep to give the emulated architecture time to run @pytest.mark.parametrize( "docker", ["FTLCONF_dns_upstreams=1.2.3.4;5.6.7.8#1234"], indirect=True ) def test_ftlconf_dns_upstreams(docker): func = docker.run("echo ${FTLCONF_dns_upstreams}") assert "1.2.3.4;5.6.7.8#1234" in func.stdout func = docker.run(""" sleep 5 pihole-FTL --config dns.upstreams """) assert "[ 1.2.3.4, 5.6.7.8#1234 ]" in func.stdout CMD_SETUP_WEB_PASSWORD = ". bash_functions.sh ; setup_web_password" def test_random_password_assigned_fresh_start(docker): func = docker.run(CMD_SETUP_WEB_PASSWORD) assert "assigning random password:" in func.stdout @pytest.mark.parametrize( "docker", ["FTLCONF_webserver_api_password=1234567890"], indirect=True ) def test_password_set_by_envvar(docker): func = docker.run(CMD_SETUP_WEB_PASSWORD) assert "Assigning password defined by Environment Variable" in func.stdout ================================================ FILE: test/tests/test_general.py ================================================ import pytest import os # Adding 5 seconds sleep to give the emulated architecture time to run @pytest.mark.parametrize("docker", ["PIHOLE_UID=456"], indirect=True) def test_pihole_uid_env_var(docker): func = docker.run("echo ${PIHOLE_UID}") assert "456" in func.stdout func = docker.run(""" sleep 5 id -u pihole """) assert "456" in func.stdout # Adding 5 seconds sleep to give the emulated architecture time to run @pytest.mark.parametrize("docker", ["PIHOLE_GID=456"], indirect=True) def test_pihole_gid_env_var(docker): func = docker.run("echo ${PIHOLE_GID}") assert "456" in func.stdout func = docker.run(""" sleep 5 id -g pihole """) assert "456" in func.stdout def test_pihole_ftl_version(docker): func = docker.run("pihole-FTL -vv") assert func.rc == 0 assert "Version:" in func.stdout @pytest.mark.skipif( not os.environ.get("CIPLATFORM"), reason="CIPLATFORM environment variable not set, running locally", ) def test_pihole_ftl_architecture(docker): func = docker.run("pihole-FTL -vv") assert func.rc == 0 assert "Architecture:" in func.stdout # Get the expected architecture from CIPLATFORM environment variable platform = os.environ.get("CIPLATFORM") assert platform in func.stdout # Wait for FTL to start up, then stop the container gracefully # Finally, check the container logs to see if FTL was shut down cleanly def test_pihole_ftl_starts_and_shuts_down_cleanly(docker): import subprocess import time # Get the container ID from the docker fixture container_id = docker.backend.name # Wait for FTL to fully start up by checking logs max_wait_time = 60 # Maximum wait time in seconds start_time = time.time() ftl_started = False while time.time() - start_time < max_wait_time: result = subprocess.run( ["docker", "logs", container_id], capture_output=True, text=True ) if "########## FTL started" in result.stdout: ftl_started = True break time.sleep(1) # Check every second assert ftl_started, f"FTL did not start within {max_wait_time} seconds" # Stop the container gracefully (sends SIGTERM) subprocess.run(["docker", "stop", container_id], check=True) # Get the container logs result = subprocess.run( ["docker", "logs", container_id], capture_output=True, text=True ) # Check for clean shutdown messages in the logs assert "INFO: ########## FTL terminated after" in result.stdout assert "(code 0)" in result.stdout def test_cronfile_valid(docker): func = docker.run(""" /usr/bin/crontab /crontab.txt crond -d 8 -L /cron.log grep 'parse error' /cron.log """) assert "parse error" not in func.stdout ================================================ FILE: test/tox.ini ================================================ [tox] envlist = py3 [testenv:py3] allowlist_externals = docker deps = -rrequirements.txt passenv = CIPLATFORM setenv = COLUMNS=120 PY_COLORS=1 commands = # Build the Docker image for testing depending on the architecture, fall back to 'local' if not set # This allows us to run the tests on the host architecture if not on CI docker buildx build --load --platform={env:CIPLATFORM:local} --progress plain -f ../src/Dockerfile -t pihole:CI_container ../src/ # run the tests # # Not using > 1 cores as it causes random issues with the emulated architectures pytest {posargs:-vv} ./tests/