├── .dockerignore ├── .editorconfig ├── .editorconfig-checker.json ├── .flake8 ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── no-response.yml ├── pull_request_template.md └── workflows │ ├── push.yml │ └── release.yml ├── .gitignore ├── .hadolint.yaml ├── .markdown-lint.yml ├── .yamllint.yaml ├── Dockerfile ├── LICENSE ├── MAINTAINERS.md ├── PRINCIPALS.md ├── README.md ├── VERSION ├── actionlint.yml ├── build-functions ├── check-commands.sh ├── get-public-image-config.sh └── gh-functions.sh ├── build-latest.sh ├── build.sh ├── configuration ├── configuration.py ├── extra.py ├── ldap │ ├── extra.py │ └── ldap_config.py ├── logging.py └── plugins.py ├── docker-compose.override.yml.example ├── docker-compose.test.override.yml ├── docker-compose.test.yml ├── docker-compose.yml ├── docker ├── configuration.docker.py ├── docker-entrypoint.sh ├── housekeeping.sh ├── launch-netbox.sh ├── ldap_config.docker.py ├── nginx-unit.json └── unit.list ├── env ├── netbox.env ├── postgres.env ├── redis-cache.env └── redis.env ├── pyproject.toml ├── release.sh ├── renovate.json ├── requirements-container.txt ├── test-configuration └── test_config.py └── test.sh /.dockerignore: -------------------------------------------------------------------------------- 1 | .git* 2 | *.md 3 | build* 4 | docker-compose* 5 | env 6 | test-configuration 7 | .netbox/.git* 8 | .netbox/.pre-commit-config.yaml 9 | .netbox/.readthedocs.yaml 10 | .netbox/.tx 11 | .netbox/contrib 12 | .netbox/scripts 13 | .netbox/upgrade.sh 14 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.py] 11 | indent_size = 4 12 | 13 | [VERSION] 14 | insert_final_newline = false 15 | -------------------------------------------------------------------------------- /.editorconfig-checker.json: -------------------------------------------------------------------------------- 1 | { 2 | "Verbose": false, 3 | "Debug": false, 4 | "IgnoreDefaults": false, 5 | "SpacesAfterTabs": false, 6 | "NoColor": false, 7 | "Exclude": ["LICENSE", "\\.initializers", "\\.vscode"], 8 | "AllowedContentTypes": [], 9 | "PassedFiles": [], 10 | "Disable": { 11 | "EndOfLine": false, 12 | "Indentation": false, 13 | "InsertFinalNewline": false, 14 | "TrimTrailingWhitespace": false, 15 | "IndentSize": true, 16 | "MaxLineLength": false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 100 3 | extend-ignore = E203, W503 4 | per-file-ignores = 5 | configuration/*:E131,E251,E266,E302,E305,E501,E722 6 | startup_scripts/startup_script_utils/__init__.py:F401 7 | docker/*:E266,E722 8 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | - cimnine 5 | - tobiasge 6 | patreon: # Replace with a single Patreon username 7 | open_collective: # Replace with a single Open Collective username 8 | ko_fi: # Replace with a single Ko-fi username 9 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 10 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 11 | liberapay: # Replace with a single Liberapay username 12 | issuehunt: # Replace with a single IssueHunt username 13 | otechie: # Replace with a single Otechie username 14 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 15 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Create a report about a malfunction of the Docker setup 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | Please only raise an issue if you're certain that you've found a bug. 8 | Else, see these other means to get help: 9 | 10 | - See our troubleshooting section: 11 | https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting 12 | - Have a look at the rest of the wiki: 13 | https://github.com/netbox-community/netbox-docker/wiki 14 | - Check the release notes: 15 | https://github.com/netbox-community/netbox-docker/releases 16 | - Look through the issues already resolved: 17 | https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed 18 | 19 | If you did not find what you're looking for, 20 | try the help of our community: 21 | 22 | - Post to Github Discussions: 23 | https://github.com/netbox-community/netbox-docker/discussions 24 | - Join the `#netbox-docker` channel on our Slack: 25 | https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ 26 | - Ask on the NetBox mailing list: 27 | https://groups.google.com/d/forum/netbox-discuss 28 | 29 | Please don't open an issue to open a PR. 30 | Just submit the PR, that's good enough. 31 | - type: textarea 32 | id: current-behavior 33 | attributes: 34 | label: Current Behavior 35 | description: Please describe what you did and how you think it misbehaved 36 | placeholder: I tried to … by doing …, but it … 37 | validations: 38 | required: true 39 | - type: textarea 40 | id: expected-behavior 41 | attributes: 42 | label: Expected Behavior 43 | description: Please describe what you expected instead 44 | placeholder: I expected that … when I do … 45 | validations: 46 | required: true 47 | - type: input 48 | id: docker-compose-version 49 | attributes: 50 | label: Docker Compose Version 51 | description: Please paste the output of `docker-compose version` (or `docker compose version`) 52 | placeholder: Docker Compose version vX.Y.Z 53 | validations: 54 | required: true 55 | - type: textarea 56 | id: docker-version 57 | attributes: 58 | label: Docker Version 59 | description: Please paste the output of `docker version` 60 | render: text 61 | placeholder: | 62 | Client: 63 | Cloud integration: 1.0.17 64 | Version: 20.10.8 65 | API version: 1.41 66 | Go version: go1.16.6 67 | Git commit: 3967b7d 68 | Built: Fri Jul 30 19:55:20 2021 69 | OS/Arch: darwin/amd64 70 | Context: default 71 | Experimental: true 72 | 73 | Server: Docker Engine - Community 74 | Engine: 75 | Version: 20.10.8 76 | API version: 1.41 (minimum version 1.12) 77 | Go version: go1.16.6 78 | Git commit: 75249d8 79 | Built: Fri Jul 30 19:52:10 2021 80 | OS/Arch: linux/amd64 81 | Experimental: false 82 | containerd: 83 | Version: 1.4.9 84 | GitCommit: e25210fe30a0a703442421b0f60afac609f950a3 85 | runc: 86 | Version: 1.0.1 87 | GitCommit: v1.0.1-0-g4144b63 88 | docker-init: 89 | Version: 0.19.0 90 | GitCommit: de40ad0 91 | validations: 92 | required: true 93 | - type: input 94 | id: git-rev 95 | attributes: 96 | label: The git Revision 97 | description: Please paste the output of `git rev-parse HEAD` 98 | validations: 99 | required: true 100 | - type: textarea 101 | id: git-status 102 | attributes: 103 | label: The git Status 104 | description: Please paste the output of `git status` 105 | render: text 106 | placeholder: | 107 | On branch main 108 | nothing to commit, working tree clean 109 | validations: 110 | required: true 111 | - type: input 112 | id: run-command 113 | attributes: 114 | label: Startup Command 115 | description: Please specify the command you used to start the project 116 | placeholder: docker compose up 117 | validations: 118 | required: true 119 | - type: textarea 120 | id: netbox-logs 121 | attributes: 122 | label: NetBox Logs 123 | description: Please paste the output of `docker-compose logs netbox` (or `docker compose logs netbox`) 124 | render: text 125 | placeholder: | 126 | netbox_1 | ⚙️ Applying database migrations 127 | netbox_1 | 🧬 loaded config '/etc/netbox/config/configuration.py' 128 | netbox_1 | 🧬 loaded config '/etc/netbox/config/a.py' 129 | netbox_1 | 🧬 loaded config '/etc/netbox/config/extra.py' 130 | netbox_1 | 🧬 loaded config '/etc/netbox/config/logging.py' 131 | netbox_1 | 🧬 loaded config '/etc/netbox/config/plugins.py' 132 | ... 133 | validations: 134 | required: true 135 | - type: textarea 136 | id: docker-compose-override-yml 137 | attributes: 138 | label: Content of docker-compose.override.yml 139 | description: Please paste the output of `cat docker-compose.override.yml` 140 | render: yaml 141 | placeholder: | 142 | services: 143 | netbox: 144 | ports: 145 | - '8080:8080' 146 | validations: 147 | required: true 148 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Question 4 | url: https://github.com/netbox-community/netbox-docker/discussions 5 | about: The Github Discussions are the right place to ask questions about how to use or do certain things with NetBox Docker. 6 | 7 | - name: Chat 8 | url: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ 9 | about: "Usually the quickest way to seek help with small issues is to join our #netbox-docker Slack channel." 10 | 11 | - name: Community Wiki 12 | url: https://github.com/netbox-community/netbox-docker/wiki 13 | about: | 14 | Our wiki contains information for common problems and tips for operating NetBox Docker in production. 15 | It's maintained by our excellent community. 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature or Change Request 2 | description: Request a new feature or a change of the current behavior 3 | body: 4 | - type: markdown 5 | attributes: 6 | value: | 7 | This issue type is to propose new features for the Docker setup. 8 | To just spin an idea, see the Github Discussions section, please. 9 | 10 | Before asking for help, see these links first: 11 | 12 | - See our troubleshooting section: 13 | https://github.com/netbox-community/netbox-docker/wiki/Troubleshooting 14 | - Have a look at the rest of the wiki: 15 | https://github.com/netbox-community/netbox-docker/wiki 16 | - Check the release notes: 17 | https://github.com/netbox-community/netbox-docker/releases 18 | - Look through the issues already resolved: 19 | https://github.com/netbox-community/netbox-docker/issues?q=is%3Aclosed 20 | 21 | If you did not find what you're looking for, 22 | try the help of our community: 23 | 24 | - Post to Github Discussions: 25 | https://github.com/netbox-community/netbox-docker/discussions 26 | - Join the `#netbox-docker` channel on our Slack: 27 | https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ 28 | - Ask on the NetBox mailing list: 29 | https://groups.google.com/d/forum/netbox-discuss 30 | 31 | Please don't open an issue to open a PR. 32 | Just submit the PR, that's good enough. 33 | - type: textarea 34 | id: desired-behavior 35 | attributes: 36 | label: Desired Behavior 37 | description: Please describe the desired behavior 38 | placeholder: To me, it would be useful, if … because … 39 | validations: 40 | required: true 41 | - type: textarea 42 | id: contrast-to-current 43 | attributes: 44 | label: Contrast to Current Behavior 45 | description: Please describe how the desired behavior is different from the current behavior 46 | placeholder: The current behavior is …, but this lacks … 47 | validations: 48 | required: true 49 | - type: textarea 50 | id: required-changes 51 | attributes: 52 | label: Required Changes 53 | description: If you can, please elaborate what changes will be required to implement the desired behavior 54 | placeholder: I suggest to change the file … 55 | validations: 56 | required: false 57 | - type: textarea 58 | id: discussion 59 | attributes: 60 | label: "Discussion: Benefits and Drawbacks" 61 | description: | 62 | Please make your case here: 63 | - Why do you think this project and the community will benefit from your suggestion? 64 | - What are the drawbacks of this change? Is it backwards-compatible? 65 | - Anything else that you think is relevant to the discussion of this feature/change request. 66 | placeholder: I suggest to change the file … 67 | validations: 68 | required: false 69 | -------------------------------------------------------------------------------- /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an Issue is closed for lack of response 4 | daysUntilClose: 30 5 | # Label requiring a response 6 | responseRequiredLabel: awaiting answer 7 | # Comment to post when closing an Issue for lack of response. Set to `false` to disable 8 | closeComment: > 9 | This issue has been automatically closed because there has been no response 10 | to our request for more information from the original author. 11 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 14 | 15 | 22 | 23 | Related Issue: 24 | 25 | ## New Behavior 26 | 27 | 30 | 31 | ... 32 | 33 | ## Contrast to Current Behavior 34 | 35 | 39 | 40 | ... 41 | 42 | ## Discussion: Benefits and Drawbacks 43 | 44 | 56 | 57 | ... 58 | 59 | ## Changes to the Wiki 60 | 61 | 65 | 66 | ... 67 | 68 | ## Proposed Release Note Entry 69 | 70 | 74 | 75 | ... 76 | 77 | ## Double Check 78 | 79 | 82 | 83 | - [ ] I have read the comments and followed the PR template. 84 | - [ ] I have explained my PR according to the information in the comments. 85 | - [ ] My PR targets the `develop` branch. 86 | -------------------------------------------------------------------------------- /.github/workflows/push.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: push 3 | 4 | on: 5 | push: 6 | branches-ignore: 7 | - release 8 | - renovate/** 9 | pull_request: 10 | branches-ignore: 11 | - release 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} 15 | cancel-in-progress: true 16 | 17 | jobs: 18 | lint: 19 | runs-on: ubuntu-24.04 20 | name: Checks syntax of our code 21 | permissions: 22 | contents: read 23 | packages: read 24 | statuses: write 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | # Full git history is needed to get a proper 29 | # list of changed files within `super-linter` 30 | fetch-depth: 0 31 | - name: Lint Code Base 32 | uses: super-linter/super-linter@v7 33 | env: 34 | DEFAULT_BRANCH: develop 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | SUPPRESS_POSSUM: true 37 | LINTER_RULES_PATH: / 38 | VALIDATE_ALL_CODEBASE: false 39 | VALIDATE_CHECKOV: false 40 | VALIDATE_DOCKERFILE: false 41 | VALIDATE_GITLEAKS: false 42 | VALIDATE_JSCPD: false 43 | FILTER_REGEX_EXCLUDE: (.*/)?(LICENSE|configuration/.*) 44 | EDITORCONFIG_FILE_NAME: .editorconfig-checker.json 45 | DOCKERFILE_HADOLINT_FILE_NAME: .hadolint.yaml 46 | MARKDOWN_CONFIG_FILE: .markdown-lint.yml 47 | PYTHON_BLACK_CONFIG_FILE: pyproject.toml 48 | PYTHON_FLAKE8_CONFIG_FILE: .flake8 49 | PYTHON_ISORT_CONFIG_FILE: pyproject.toml 50 | YAML_CONFIG_FILE: .yamllint.yaml 51 | build: 52 | continue-on-error: ${{ matrix.build_cmd != './build-latest.sh' }} 53 | strategy: 54 | matrix: 55 | build_cmd: 56 | - ./build-latest.sh 57 | - PRERELEASE=true ./build-latest.sh 58 | - ./build.sh feature 59 | - ./build.sh main 60 | os: 61 | - ubuntu-24.04 62 | - ubuntu-24.04-arm 63 | fail-fast: false 64 | env: 65 | GH_ACTION: enable 66 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 67 | IMAGE_NAMES: docker.io/netboxcommunity/netbox 68 | runs-on: ${{ matrix.os }} 69 | name: Builds new NetBox Docker Images 70 | steps: 71 | - id: git-checkout 72 | name: Checkout 73 | uses: actions/checkout@v4 74 | - id: buildx-setup 75 | name: Set up Docker Buildx 76 | uses: docker/setup-buildx-action@v3 77 | - id: arm-install-skopeo 78 | name: Install 'skopeo' on ARM64 79 | if: matrix.os == 'ubuntu-24.04-arm' 80 | run: | 81 | sudo apt-get install -y skopeo 82 | - id: arm-buildx-platform 83 | name: Set BUILDX_PLATFORM to ARM64 84 | if: matrix.os == 'ubuntu-24.04-arm' 85 | run: | 86 | echo "BUILDX_PLATFORM=linux/arm64" >>"${GITHUB_ENV}" 87 | - id: docker-build 88 | name: Build the image for '${{ matrix.os }}' with '${{ matrix.build_cmd }}' 89 | run: ${{ matrix.build_cmd }} 90 | env: 91 | BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} 92 | - id: arm-time-limit 93 | name: Set Netbox container start_period higher on ARM64 94 | if: matrix.os == 'ubuntu-24.04-arm' 95 | run: | 96 | echo "NETBOX_START_PERIOD=240s" >>"${GITHUB_ENV}" 97 | - id: docker-test 98 | name: Test the image 99 | run: IMAGE="${FINAL_DOCKER_TAG}" ./test.sh 100 | if: steps.docker-build.outputs.skipped != 'true' 101 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: release 3 | 4 | on: 5 | release: 6 | types: 7 | - published 8 | schedule: 9 | - cron: "45 5 * * *" 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | matrix: 16 | build: 17 | - { "cmd": "./build-latest.sh", "branch": "release" } 18 | - { "cmd": "./build.sh main", "branch": "release" } 19 | # Build pre release images from our develop branch 20 | # This is used to test the latest changes before they are merged into the main branch 21 | - { "cmd": "PRERELEASE=true ./build-latest.sh", "branch": "develop" } 22 | - { "cmd": "./build.sh feature", "branch": "develop" } 23 | platform: 24 | - linux/amd64,linux/arm64 25 | fail-fast: false 26 | runs-on: ubuntu-24.04 27 | name: Builds new NetBox Docker Images 28 | env: 29 | GH_ACTION: enable 30 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 31 | IMAGE_NAMES: docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox ghcr.io/netbox-community/netbox 32 | steps: 33 | - id: source-checkout 34 | name: Checkout 35 | uses: actions/checkout@v4 36 | with: 37 | ref: ${{ matrix.build.branch }} 38 | - id: set-netbox-docker-version 39 | name: Get Version of NetBox Docker 40 | run: echo "version=$(cat VERSION)" >>"$GITHUB_OUTPUT" 41 | shell: bash 42 | - id: check-build-needed 43 | name: Check if the build is needed for '${{ matrix.build.cmd }}' 44 | env: 45 | CHECK_ONLY: "true" 46 | run: ${{ matrix.build.cmd }} 47 | # docker.io 48 | - id: docker-io-login 49 | name: Login to docker.io 50 | uses: docker/login-action@v3 51 | with: 52 | registry: docker.io 53 | username: ${{ secrets.dockerhub_username }} 54 | password: ${{ secrets.dockerhub_password }} 55 | if: steps.check-build-needed.outputs.skipped != 'true' 56 | - id: buildx-setup 57 | name: Set up Docker Buildx 58 | uses: docker/setup-buildx-action@v3 59 | with: 60 | version: "lab:latest" 61 | driver: cloud 62 | endpoint: "netboxcommunity/netbox-default" 63 | if: steps.check-build-needed.outputs.skipped != 'true' 64 | # quay.io 65 | - id: quay-io-login 66 | name: Login to Quay.io 67 | uses: docker/login-action@v3 68 | with: 69 | registry: quay.io 70 | username: ${{ secrets.quayio_username }} 71 | password: ${{ secrets.quayio_password }} 72 | if: steps.check-build-needed.outputs.skipped != 'true' 73 | # ghcr.io 74 | - id: ghcr-io-login 75 | name: Login to GitHub Container Registry 76 | uses: docker/login-action@v3 77 | with: 78 | registry: ghcr.io 79 | username: ${{ github.repository_owner }} 80 | password: ${{ secrets.GITHUB_TOKEN }} 81 | if: steps.check-build-needed.outputs.skipped != 'true' 82 | - id: build-and-push 83 | name: Push the image 84 | run: ${{ matrix.build.cmd }} --push 85 | if: steps.check-build-needed.outputs.skipped != 'true' 86 | env: 87 | BUILDX_PLATFORM: ${{ matrix.platform }} 88 | BUILDX_BUILDER_NAME: ${{ steps.buildx-setup.outputs.name }} 89 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sql.gz 2 | .netbox 3 | .python-version 4 | docker-compose.override.yml 5 | *.pem 6 | configuration/* 7 | !configuration/configuration.py 8 | !configuration/extra.py 9 | configuration/ldap/* 10 | !configuration/ldap/extra.py 11 | !configuration/ldap/ldap_config.py 12 | !configuration/logging.py 13 | !configuration/plugins.py 14 | super-linter.log 15 | -------------------------------------------------------------------------------- /.hadolint.yaml: -------------------------------------------------------------------------------- 1 | ignored: 2 | - DL3006 3 | - DL3008 4 | - DL3003 5 | -------------------------------------------------------------------------------- /.markdown-lint.yml: -------------------------------------------------------------------------------- 1 | MD013: false 2 | MD041: false 3 | -------------------------------------------------------------------------------- /.yamllint.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | rules: 3 | line-length: 4 | max: 160 5 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG FROM 2 | FROM ${FROM} AS builder 3 | 4 | COPY --from=ghcr.io/astral-sh/uv:0.7 /uv /usr/local/bin/ 5 | RUN export DEBIAN_FRONTEND=noninteractive \ 6 | && apt-get update -qq \ 7 | && apt-get upgrade \ 8 | --yes -qq --no-install-recommends \ 9 | && apt-get install \ 10 | --yes -qq --no-install-recommends \ 11 | build-essential \ 12 | ca-certificates \ 13 | libldap-dev \ 14 | libpq-dev \ 15 | libsasl2-dev \ 16 | libssl-dev \ 17 | libxml2-dev \ 18 | libxmlsec1 \ 19 | libxmlsec1-dev \ 20 | libxmlsec1-openssl \ 21 | libxslt-dev \ 22 | pkg-config \ 23 | python3-dev \ 24 | && /usr/local/bin/uv venv /opt/netbox/venv 25 | 26 | ARG NETBOX_PATH 27 | COPY ${NETBOX_PATH}/requirements.txt requirements-container.txt / 28 | ENV VIRTUAL_ENV=/opt/netbox/venv 29 | RUN \ 30 | # Gunicorn is not needed because we use Nginx Unit 31 | sed -i -e '/gunicorn/d' /requirements.txt && \ 32 | # We need 'social-auth-core[all]' in the Docker image. But if we put it in our own requirements-container.txt 33 | # we have potential version conflicts and the build will fail. 34 | # That's why we just replace it in the original requirements.txt. 35 | sed -i -e 's/social-auth-core/social-auth-core\[all\]/g' /requirements.txt && \ 36 | # The same is true for 'django-storages' 37 | sed -i -e 's/django-storages/django-storages\[azure,boto3,dropbox,google,libcloud,sftp\]/g' /requirements.txt && \ 38 | /usr/local/bin/uv pip install \ 39 | -r /requirements.txt \ 40 | -r /requirements-container.txt 41 | 42 | ### 43 | # Main stage 44 | ### 45 | 46 | ARG FROM 47 | FROM ${FROM} AS main 48 | 49 | COPY docker/unit.list /etc/apt/sources.list.d/unit.list 50 | ADD --chmod=444 --chown=0:0 https://unit.nginx.org/keys/nginx-keyring.gpg /usr/share/keyrings/nginx-keyring.gpg 51 | RUN export DEBIAN_FRONTEND=noninteractive \ 52 | && apt-get update -qq \ 53 | && apt-get upgrade \ 54 | --yes -qq --no-install-recommends \ 55 | && apt-get install \ 56 | --yes -qq --no-install-recommends \ 57 | bzip2 \ 58 | ca-certificates \ 59 | curl \ 60 | libldap-common \ 61 | libpq5 \ 62 | libxmlsec1-openssl \ 63 | openssh-client \ 64 | openssl \ 65 | python3 \ 66 | tini \ 67 | unit-python3.12=1.34.2-1~noble \ 68 | unit=1.34.2-1~noble \ 69 | && rm -rf /var/lib/apt/lists/* 70 | 71 | # Copy the modified 'requirements*.txt' files, to have the files actually used during installation 72 | COPY --from=builder /requirements.txt /requirements-container.txt /opt/netbox/ 73 | COPY --from=builder /usr/local/bin/uv /usr/local/bin/ 74 | COPY --from=builder /opt/netbox/venv /opt/netbox/venv 75 | 76 | ARG NETBOX_PATH 77 | COPY ${NETBOX_PATH} /opt/netbox 78 | 79 | COPY docker/configuration.docker.py /opt/netbox/netbox/netbox/configuration.py 80 | COPY docker/ldap_config.docker.py /opt/netbox/netbox/netbox/ldap_config.py 81 | COPY docker/docker-entrypoint.sh /opt/netbox/docker-entrypoint.sh 82 | COPY docker/housekeeping.sh /opt/netbox/housekeeping.sh 83 | COPY docker/launch-netbox.sh /opt/netbox/launch-netbox.sh 84 | COPY configuration/ /etc/netbox/config/ 85 | COPY docker/nginx-unit.json /etc/unit/ 86 | COPY VERSION /opt/netbox/VERSION 87 | 88 | WORKDIR /opt/netbox/netbox 89 | 90 | # Must set permissions for '/opt/netbox/netbox/media' directory 91 | # to g+w so that pictures can be uploaded to netbox. 92 | RUN mkdir -p static /opt/unit/state/ /opt/unit/tmp/ \ 93 | && chown -R unit:root /opt/unit/ media reports scripts \ 94 | && chmod -R g+w /opt/unit/ media reports scripts \ 95 | && cd /opt/netbox/ && SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python -m mkdocs build \ 96 | --config-file /opt/netbox/mkdocs.yml --site-dir /opt/netbox/netbox/project-static/docs/ \ 97 | && DEBUG="true" SECRET_KEY="dummyKeyWithMinimumLength-------------------------" /opt/netbox/venv/bin/python /opt/netbox/netbox/manage.py collectstatic --no-input \ 98 | && mkdir /opt/netbox/netbox/local \ 99 | && echo "build: Docker-$(cat /opt/netbox/VERSION)" > /opt/netbox/netbox/local/release.yaml 100 | 101 | ENV LANG=C.utf8 PATH=/opt/netbox/venv/bin:$PATH VIRTUAL_ENV=/opt/netbox/venv UV_NO_CACHE=1 102 | ENTRYPOINT [ "/usr/bin/tini", "--" ] 103 | 104 | CMD [ "/opt/netbox/docker-entrypoint.sh", "/opt/netbox/launch-netbox.sh" ] 105 | 106 | LABEL netbox.original-tag="" \ 107 | netbox.git-branch="" \ 108 | netbox.git-ref="" \ 109 | netbox.git-url="" \ 110 | # See https://github.com/opencontainers/image-spec/blob/master/annotations.md#pre-defined-annotation-keys 111 | org.opencontainers.image.created="" \ 112 | org.opencontainers.image.title="NetBox Docker" \ 113 | org.opencontainers.image.description="A container based distribution of NetBox, the free and open IPAM and DCIM solution." \ 114 | org.opencontainers.image.licenses="Apache-2.0" \ 115 | org.opencontainers.image.authors="The netbox-docker contributors." \ 116 | org.opencontainers.image.vendor="The netbox-docker contributors." \ 117 | org.opencontainers.image.url="https://github.com/netbox-community/netbox-docker" \ 118 | org.opencontainers.image.documentation="https://github.com/netbox-community/netbox-docker/wiki" \ 119 | org.opencontainers.image.source="https://github.com/netbox-community/netbox-docker.git" \ 120 | org.opencontainers.image.revision="" \ 121 | org.opencontainers.image.version="" 122 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | -------------------------------------------------------------------------------- /MAINTAINERS.md: -------------------------------------------------------------------------------- 1 | # Maintainers of _NetBox Docker_ 2 | 3 | This file lists all currently recognized maintainers of the _NetBox Docker_ project in alphabetical order: 4 | 5 | - @cimnine 6 | - @tobiasge 7 | 8 | ## Stepping Down 9 | 10 | Every maintainer is a volunteer and may step down as maintainer at any time without providing any reason. 11 | To make this explicit, the maintainer is asked to update this file. 12 | 13 | The last maintainer stepping down is asked to archive the project on GitHub to indicate that the project is no longer maintained. 14 | 15 | ## Signing up 16 | 17 | Everyone is welcome to sign up as maintainer by creating a PR and add their own username to the list. 18 | The current maintainers shall discuss the application. 19 | They may turn down an application if they don't feel confident that the new maintainer is a positive addition. 20 | -------------------------------------------------------------------------------- /PRINCIPALS.md: -------------------------------------------------------------------------------- 1 | # Development, Maintenance and Community Principals for _NetBox Docker_ 2 | 3 | These principals shall guide the development and the maintenance of _NetBox Docker_. 4 | 5 | ## Basic principals 6 | 7 | This project is maintained on voluntary basis. 8 | Everyone is asked to respect that. 9 | 10 | This means, that … 11 | 12 | - … sometimes features are not implemented as fast as one might like -- or not at all. 13 | - … sometimes nobody is looking at bugs, or they are not fixed as fast as one might like -- or not at all. 14 | - … sometimes PRs are not reviewed for an extended period. 15 | 16 | Everyone is welcome to provide improvements and bugfixes to the benefit of everyone else. 17 | 18 | ## Development Principals 19 | 20 | The goal of the _NetBox Docker_ project is to provide a container to run the basic NetBox project. 21 | The container should feel like a native container -- as if it were provided by NetBox itself: 22 | 23 | - Configuration via environment variables where feasible. 24 | - Except: Whenever a complex type such as a `dict` is required as value of a configuration setting, 25 | then it shall not be provided through an environment variable. 26 | - Configuration of secrets via secret files. 27 | - Log output to standard out (STDOUT/`&1`) / standard error (STDERR/`&2`). 28 | - Volumes for data and cache directories. 29 | - Otherwise, no mounts shall be necessary. 30 | - Runs a non-root user by default. 31 | - One process / role for each instance. 32 | 33 | The container generally does not provide more features than the basic NetBox project itself provides. 34 | It may provide additional Python dependencies than the upstream project, 35 | so that all configurable features of NetBox can be used in the container without further modification. 36 | The container may provide helpers, so that it feels and behaves like a native container. 37 | 38 | The container does not bundle any community plugins. 39 | 40 | ## Maintenance Principals 41 | 42 | The main goals of maintaining _NetBox Docker_ are: 43 | 44 | - Keeping the project at a high quality level. 45 | - Keeping the maintenance effort minimal. 46 | - Coordinating development efforts. 47 | 48 | The following guidelines help us to achieve these goals: 49 | 50 | - As many maintenance tasks as possible shall be automated or scripted. 51 | - All manual tasks must be documented. 52 | - All changes are reviewed by at least one maintainer. 53 | - Changes of maintainers are reviewed by at least one other maintainer. 54 | (Except if there's only one maintainer left.) 55 | - The infrastructure beyond what GitHub provides shall be kept to a minimum. 56 | - On request, every maintainer shall get access to infrastructure that is beyond GitHub 57 | (at the time of writing that's _Docker Hub_ and _Quay_ in particular). 58 | 59 | ## Community Principals 60 | 61 | This project is developed by the NetBox community for the NetBox community. 62 | We welcome contributions, as long as they are in line with the principals above. 63 | 64 | The maintainers of NetBox Docker are not the support team. 65 | The community is expected to help each other out. 66 | 67 | Always remember: 68 | Behind every screen (or screen-reader) on the other end is a fellow human. 69 | Be nice and respectful, thankful for help, 70 | and value ideas and contributions, 71 | even when they don't fit the goals. 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netbox-docker 2 | 3 | [![GitHub release (latest by date)](https://img.shields.io/github/v/release/netbox-community/netbox-docker)][github-release] 4 | [![GitHub stars](https://img.shields.io/github/stars/netbox-community/netbox-docker)][github-stargazers] 5 | ![GitHub closed pull requests](https://img.shields.io/github/issues-pr-closed-raw/netbox-community/netbox-docker) 6 | ![Github release workflow](https://img.shields.io/github/actions/workflow/status/netbox-community/netbox-docker/release.yml?branch=release) 7 | ![Docker Pulls](https://img.shields.io/docker/pulls/netboxcommunity/netbox) 8 | [![GitHub license](https://img.shields.io/github/license/netbox-community/netbox-docker)][netbox-docker-license] 9 | 10 | [The GitHub repository][netbox-docker-github] houses the components needed to build NetBox as a container. 11 | Images are built regularly using the code in that repository 12 | and are pushed to [Docker Hub][netbox-dockerhub], 13 | [Quay.io][netbox-quayio] and [GitHub Container Registry][netbox-ghcr]. 14 | _NetBox Docker_ is a project developed and maintained by the _NetBox_ community. 15 | 16 | Do you have any questions? 17 | Before opening an issue on GitHub, 18 | please join [our Slack][netbox-docker-slack] 19 | and ask for help in the [`#netbox-docker`][netbox-docker-slack-channel] channel, 20 | or start a new [GitHub Discussion][github-discussions]. 21 | 22 | [github-stargazers]: https://github.com/netbox-community/netbox-docker/stargazers 23 | [github-release]: https://github.com/netbox-community/netbox-docker/releases 24 | [netbox-dockerhub]: https://hub.docker.com/r/netboxcommunity/netbox/ 25 | [netbox-quayio]: https://quay.io/repository/netboxcommunity/netbox 26 | [netbox-ghcr]: https://github.com/netbox-community/netbox-docker/pkgs/container/netbox 27 | [netbox-docker-github]: https://github.com/netbox-community/netbox-docker/ 28 | [netbox-docker-slack]: https://join.slack.com/t/netdev-community/shared_invite/zt-mtts8g0n-Sm6Wutn62q_M4OdsaIycrQ 29 | [netbox-docker-slack-channel]: https://netdev-community.slack.com/archives/C01P0GEVBU7 30 | [netbox-slack-channel]: https://netdev-community.slack.com/archives/C01P0FRSXRV 31 | [netbox-docker-license]: https://github.com/netbox-community/netbox-docker/blob/release/LICENSE 32 | [github-discussions]: https://github.com/netbox-community/netbox-docker/discussions 33 | 34 | ## Quickstart 35 | 36 | To get _NetBox Docker_ up and running run the following commands. 37 | There is a more complete [_Getting Started_ guide on our wiki][wiki-getting-started] which explains every step. 38 | 39 | ```bash 40 | git clone -b release https://github.com/netbox-community/netbox-docker.git 41 | cd netbox-docker 42 | tee docker-compose.override.yml < We recommend to use either the `vX.Y.Z-a.b.c` tags or the `vX.Y-a.b.c` tags in production! 72 | 73 | - `vX.Y.Z-a.b.c`, `vX.Y-a.b.c`: 74 | These are release builds containing _NetBox version_ `vX.Y.Z`. 75 | They contain the support files of _NetBox Docker version_ `a.b.c`. 76 | You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. 77 | These images are automatically built from [the corresponding releases of NetBox][netbox-releases]. 78 | - `latest-a.b.c`: 79 | These are release builds, containing the latest stable version of NetBox. 80 | They contain the support files of _NetBox Docker version_ `a.b.c`. 81 | You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. 82 | - `snapshot-a.b.c`: 83 | These are prerelease builds. 84 | They contain the support files of _NetBox Docker version_ `a.b.c`. 85 | You must use _NetBox Docker version_ `a.b.c` to guarantee the compatibility. 86 | These images are automatically built from the [`main` branch of NetBox][netbox-main]. 87 | 88 | For each of the above tag, there is an extra tag: 89 | 90 | - `vX.Y.Z`, `vX.Y`: 91 | This is the same version as `vX.Y.Z-a.b.c` (or `vX.Y-a.b.c`, respectively). 92 | - `latest` 93 | This is the same version as `latest-a.b.c`. 94 | It always points to the latest version of _NetBox Docker_. 95 | - `snapshot` 96 | This is the same version as `snapshot-a.b.c`. 97 | It always points to the latest version of _NetBox Docker_. 98 | 99 | [netbox-releases]: https://github.com/netbox-community/netbox/releases 100 | [netbox-main]: https://github.com/netbox-community/netbox/tree/main 101 | 102 | ## Documentation 103 | 104 | Please refer [to our wiki on GitHub][netbox-docker-wiki] for further information on how to use the NetBox Docker image properly. 105 | The wiki covers advanced topics such as using files for secrets, configuring TLS, deployment to Kubernetes, monitoring and configuring LDAP. 106 | 107 | Our wiki is a community effort. 108 | Feel free to correct errors, update outdated information or provide additional guides and insights. 109 | 110 | [netbox-docker-wiki]: https://github.com/netbox-community/netbox-docker/wiki/ 111 | 112 | ## Getting Help 113 | 114 | Feel free to ask questions in our [GitHub Community][netbox-community] 115 | or [join our Slack][netbox-docker-slack] and ask [in our channel `#netbox-docker`][netbox-docker-slack-channel], 116 | which is free to use and where there are almost always people online that can help you. 117 | 118 | If you need help with using NetBox or developing for it or against it's API 119 | you may find [the `#netbox` channel][netbox-slack-channel] on the same Slack instance very helpful. 120 | 121 | [netbox-community]: https://github.com/netbox-community/netbox-docker/discussions 122 | 123 | ## Dependencies 124 | 125 | This project relies only on _Docker_ and _docker-compose_ meeting these requirements: 126 | 127 | - The _Docker version_ must be at least `20.10.10`. 128 | - The _containerd version_ must be at least `1.5.6`. 129 | - The _docker-compose version_ must be at least `1.28.0`. 130 | 131 | To check the version installed on your system run `docker --version` and `docker compose version`. 132 | 133 | ## Updating 134 | 135 | Please read [the release notes][releases] carefully when updating to a new image version. 136 | Note that the version of the NetBox Docker container image must stay in sync with the version of the Git repository. 137 | 138 | If you update for the first time, be sure [to follow our _How To Update NetBox Docker_ guide in the wiki][netbox-docker-wiki-updating]. 139 | 140 | [releases]: https://github.com/netbox-community/netbox-docker/releases 141 | [netbox-docker-wiki-updating]: https://github.com/netbox-community/netbox-docker/wiki/Updating 142 | 143 | ## Rebuilding the Image 144 | 145 | `./build.sh` can be used to rebuild the container image. 146 | See `./build.sh --help` for more information or `./build-latest.sh` for an example. 147 | 148 | For more details on custom builds [consult our wiki][netbox-docker-wiki-build]. 149 | 150 | [netbox-docker-wiki-build]: https://github.com/netbox-community/netbox-docker/wiki/Build 151 | 152 | ## Tests 153 | 154 | We have a test script. 155 | It runs NetBox's own unit tests and ensures that NetBox starts: 156 | 157 | ```bash 158 | IMAGE=docker.io/netboxcommunity/netbox:latest ./test.sh 159 | ``` 160 | 161 | ## Support 162 | 163 | This repository is currently maintained by the community. 164 | The community is expected to help each other. 165 | 166 | Please consider sponsoring the maintainers of this project. 167 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 3.3.0 -------------------------------------------------------------------------------- /actionlint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | paths: 3 | .github/workflows/**/*.{yml,yaml}: 4 | ignore: 5 | - ".*ubuntu-24.04-arm.*" 6 | -------------------------------------------------------------------------------- /build-functions/check-commands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | NEEDED_COMMANDS="curl jq docker skopeo" 4 | for c in $NEEDED_COMMANDS; do 5 | if ! command -v "$c" &>/dev/null; then 6 | echo "⚠️ '$c' is not installed. Can't proceed with build." 7 | exit 1 8 | fi 9 | done 10 | -------------------------------------------------------------------------------- /build-functions/get-public-image-config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | check_if_tags_exists() { 4 | local image=$1 5 | local tag=$2 6 | skopeo list-tags "docker://$image" | jq -r ".Tags | contains([\"$tag\"])" 7 | } 8 | 9 | get_image_label() { 10 | local label=$1 11 | local image=$2 12 | skopeo inspect "docker://$image" | jq -r ".Labels[\"$label\"]" 13 | } 14 | 15 | get_image_last_layer() { 16 | local image=$1 17 | skopeo inspect "docker://$image" | jq -r ".Layers | last" 18 | } 19 | -------------------------------------------------------------------------------- /build-functions/gh-functions.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ### 4 | # A regular echo, that only prints if ${GH_ACTION} is defined. 5 | ### 6 | gh_echo() { 7 | if [ -n "${GH_ACTION}" ]; then 8 | echo "${@}" 9 | fi 10 | } 11 | 12 | ### 13 | # Prints the output to the file defined in ${GITHUB_ENV}. 14 | # Only executes if ${GH_ACTION} is defined. 15 | # Example Usage: gh_env "FOO_VAR=bar_value" 16 | ### 17 | gh_env() { 18 | if [ -n "${GH_ACTION}" ]; then 19 | echo "${@}" >>"${GITHUB_ENV}" 20 | fi 21 | } 22 | 23 | ### 24 | # Prints the output to the file defined in ${GITHUB_OUTPUT}. 25 | # Only executes if ${GH_ACTION} is defined. 26 | # Example Usage: gh_env "FOO_VAR=bar_value" 27 | ### 28 | gh_out() { 29 | if [ -n "${GH_ACTION}" ]; then 30 | echo "${@}" >>"$GITHUB_OUTPUT" 31 | fi 32 | } 33 | -------------------------------------------------------------------------------- /build-latest.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Builds the latest released version 3 | 4 | # Check if we have everything needed for the build 5 | source ./build-functions/check-commands.sh 6 | 7 | source ./build-functions/gh-functions.sh 8 | 9 | echo "▶️ $0 $*" 10 | 11 | CURL_ARGS=( 12 | --silent 13 | ) 14 | 15 | ### 16 | # Checking for the presence of GITHUB_TOKEN 17 | ### 18 | if [ -n "${GITHUB_TOKEN}" ]; then 19 | echo "🗝 Performing authenticated Github API calls." 20 | CURL_ARGS+=( 21 | --header "Authorization: Bearer ${GITHUB_TOKEN}" 22 | ) 23 | else 24 | echo "🕶 Performing unauthenticated Github API calls. This might result in lower Github rate limits!" 25 | fi 26 | 27 | ### 28 | # Checking if PRERELEASE is either unset, 'true' or 'false' 29 | ### 30 | if [ -n "${PRERELEASE}" ] && 31 | { [ "${PRERELEASE}" != "true" ] && [ "${PRERELEASE}" != "false" ]; }; then 32 | 33 | if [ -z "${DEBUG}" ]; then 34 | echo "⚠️ PRERELEASE must be either unset, 'true' or 'false', but was '${PRERELEASE}'!" 35 | exit 1 36 | else 37 | echo "⚠️ Would exit here with code '1', but DEBUG is enabled." 38 | fi 39 | fi 40 | 41 | ### 42 | # Calling Github to get the latest version 43 | ### 44 | ORIGINAL_GITHUB_REPO="netbox-community/netbox" 45 | GITHUB_REPO="${GITHUB_REPO-$ORIGINAL_GITHUB_REPO}" 46 | URL_RELEASES="https://api.github.com/repos/${GITHUB_REPO}/releases" 47 | 48 | # Composing the JQ commans to extract the most recent version number 49 | JQ_LATEST="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==${PRERELEASE-false}) | .tag_name" 50 | 51 | CURL="curl" 52 | 53 | # Querying the Github API to fetch the most recent version number 54 | VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_LATEST}" 2>/dev/null) 55 | 56 | ### 57 | # Check if the prerelease version is actually higher than stable version 58 | ### 59 | if [ "${PRERELEASE}" == "true" ]; then 60 | JQ_STABLE="group_by(.prerelease) | .[] | sort_by(.published_at) | reverse | .[0] | select(.prerelease==false) | .tag_name" 61 | STABLE_VERSION=$($CURL "${CURL_ARGS[@]}" "${URL_RELEASES}" | jq -r "${JQ_STABLE}" 2>/dev/null) 62 | 63 | MAJOR_STABLE=$(expr "${STABLE_VERSION}" : 'v\([0-9]\+\)') 64 | MINOR_STABLE=$(expr "${STABLE_VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') 65 | MAJOR_UNSTABLE=$(expr "${VERSION}" : 'v\([0-9]\+\)') 66 | MINOR_UNSTABLE=$(expr "${VERSION}" : 'v[0-9]\+\.\([0-9]\+\)') 67 | 68 | if { 69 | [ "${MAJOR_STABLE}" -eq "${MAJOR_UNSTABLE}" ] && 70 | [ "${MINOR_STABLE}" -ge "${MINOR_UNSTABLE}" ] 71 | } || [ "${MAJOR_STABLE}" -gt "${MAJOR_UNSTABLE}" ]; then 72 | 73 | echo "❎ Latest unstable version '${VERSION}' is not higher than the latest stable version '$STABLE_VERSION'." 74 | if [ -z "$DEBUG" ]; then 75 | gh_out "skipped=true" 76 | exit 0 77 | else 78 | echo "⚠️ Would exit here with code '0', but DEBUG is enabled." 79 | fi 80 | fi 81 | fi 82 | 83 | # shellcheck disable=SC2068 84 | ./build.sh "${VERSION}" $@ 85 | exit $? 86 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Clones the NetBox repository with git from Github and builds the Dockerfile 3 | 4 | echo "▶️ $0 $*" 5 | 6 | set -e 7 | 8 | if [ "${1}x" == "x" ] || [ "${1}" == "--help" ] || [ "${1}" == "-h" ]; then 9 | _BOLD=$(tput bold) 10 | _GREEN=$(tput setaf 2) 11 | _CYAN=$(tput setaf 6) 12 | _CLEAR=$(tput sgr0) 13 | 14 | cat < [--push] 16 | 17 | branch The branch or tag to build. Required. 18 | --push Pushes the built container image to the registry. 19 | 20 | ${_BOLD}You can use the following ENV variables to customize the build:${_CLEAR} 21 | 22 | SRC_ORG Which fork of netbox to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). 23 | ${_GREEN}Default:${_CLEAR} netbox-community 24 | 25 | SRC_REPO The name of the repository to use (i.e. github.com/\${SRC_ORG}/\${SRC_REPO}). 26 | ${_GREEN}Default:${_CLEAR} netbox 27 | 28 | URL Where to fetch the code from. 29 | Must be a git repository. Can be private. 30 | ${_GREEN}Default:${_CLEAR} https://github.com/\${SRC_ORG}/\${SRC_REPO}.git 31 | 32 | NETBOX_PATH The path where netbox will be checkout out. 33 | Must not be outside of the netbox-docker repository (because of Docker)! 34 | ${_GREEN}Default:${_CLEAR} .netbox 35 | 36 | SKIP_GIT If defined, git is not invoked and \${NETBOX_PATH} will not be altered. 37 | This may be useful, if you are manually managing the NETBOX_PATH. 38 | ${_GREEN}Default:${_CLEAR} undefined 39 | 40 | TAG The version part of the image tag. 41 | ${_GREEN}Default:${_CLEAR} 42 | When =main: snapshot 43 | Else: same as 44 | 45 | IMAGE_NAMES The names used for the image including the registry 46 | Used for tagging the image. 47 | ${_GREEN}Default:${_CLEAR} docker.io/netboxcommunity/netbox 48 | ${_CYAN}Example:${_CLEAR} 'docker.io/netboxcommunity/netbox quay.io/netboxcommunity/netbox' 49 | 50 | DOCKER_TAG The name of the tag which is applied to the image. 51 | Useful for pushing into another registry than hub.docker.com. 52 | ${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:\${TAG} 53 | 54 | DOCKER_SHORT_TAG The name of the short tag which is applied to the 55 | image. This is used to tag all patch releases to their 56 | containing version e.g. v2.5.1 -> v2.5 57 | ${_GREEN}Default:${_CLEAR} \${DOCKER_REGISTRY}/\${DOCKER_ORG}/\${DOCKER_REPO}:. 58 | 59 | DOCKERFILE The name of Dockerfile to use. 60 | ${_GREEN}Default:${_CLEAR} Dockerfile 61 | 62 | DOCKER_FROM The base image to use. 63 | ${_GREEN}Default:${_CLEAR} 'ubuntu:24.04' 64 | 65 | BUILDX_PLATFORMS 66 | Specifies the platform(s) to build the image for. 67 | ${_CYAN}Example:${_CLEAR} 'linux/amd64,linux/arm64' 68 | ${_GREEN}Default:${_CLEAR} 'linux/amd64' 69 | 70 | BUILDX_BUILDER_NAME 71 | If defined, the image build will be assigned to the given builder. 72 | If you specify this variable, make sure that the builder exists. 73 | If this value is not defined, a new builx builder with the directory name of the 74 | current directory (i.e. '$(basename "${PWD}")') is created." 75 | ${_CYAN}Example:${_CLEAR} 'clever_lovelace' 76 | ${_GREEN}Default:${_CLEAR} undefined 77 | 78 | BUILDX_REMOVE_BUILDER 79 | If defined (and only if BUILDX_BUILDER_NAME is undefined), 80 | then the buildx builder created by this script will be removed after use. 81 | This is useful if you build NetBox Docker on an automated system that does 82 | not manage the builders for you. 83 | ${_CYAN}Example:${_CLEAR} 'on' 84 | ${_GREEN}Default:${_CLEAR} undefined 85 | 86 | HTTP_PROXY The proxy to use for http requests. 87 | ${_CYAN}Example:${_CLEAR} http://proxy.domain.tld:3128 88 | ${_GREEN}Default:${_CLEAR} undefined 89 | 90 | NO_PROXY Comma-separated list of domain extensions proxy should not be used for. 91 | ${_CYAN}Example:${_CLEAR} .domain1.tld,.domain2.tld 92 | ${_GREEN}Default:${_CLEAR} undefined 93 | 94 | DEBUG If defined, the script does not stop when certain checks are unsatisfied. 95 | ${_GREEN}Default:${_CLEAR} undefined 96 | 97 | DRY_RUN Prints all build statements instead of running them. 98 | ${_GREEN}Default:${_CLEAR} undefined 99 | 100 | GH_ACTION If defined, special 'echo' statements are enabled that set the 101 | following environment variables in Github Actions: 102 | - FINAL_DOCKER_TAG: The final value of the DOCKER_TAG env variable 103 | ${_GREEN}Default:${_CLEAR} undefined 104 | 105 | CHECK_ONLY Only checks if the build is needed and sets the GH Action output. 106 | ${_GREEN}Default:${_CLEAR} undefined 107 | 108 | ${_BOLD}Examples:${_CLEAR} 109 | 110 | ${0} main 111 | This will fetch the latest 'main' branch, build a Docker Image and tag it 112 | 'netboxcommunity/netbox:snapshot'. 113 | 114 | ${0} v4.2.0 115 | This will fetch the 'v4.2.0' tag, build a Docker Image and tag it 116 | 'netboxcommunity/netbox:v4.2.0' and 'netboxcommunity/netbox:v4.2'. 117 | 118 | ${0} feature 119 | This will fetch the 'feature' branch, build a Docker Image and tag it 120 | 'netboxcommunity/netbox:feature'. 121 | 122 | SRC_ORG=cimnine ${0} feature-x 123 | This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, 124 | build a Docker Image and tag it 'netboxcommunity/netbox:feature-x'. 125 | 126 | SRC_ORG=cimnine DOCKER_ORG=cimnine ${0} feature-x 127 | This will fetch the 'feature-x' branch from https://github.com/cimnine/netbox.git, 128 | build a Docker Image and tag it 'cimnine/netbox:feature-x'. 129 | END_OF_HELP 130 | 131 | if [ "${1}x" == "x" ]; then 132 | exit 1 133 | else 134 | exit 0 135 | fi 136 | fi 137 | 138 | # Check if we have everything needed for the build 139 | source ./build-functions/check-commands.sh 140 | # Load all build functions 141 | source ./build-functions/get-public-image-config.sh 142 | source ./build-functions/gh-functions.sh 143 | 144 | IMAGE_NAMES="${IMAGE_NAMES-docker.io/netboxcommunity/netbox}" 145 | IFS=' ' read -ra IMAGE_NAMES <<<"${IMAGE_NAMES}" 146 | 147 | ### 148 | # Enabling dry-run mode 149 | ### 150 | if [ -z "${DRY_RUN}" ]; then 151 | DRY="" 152 | else 153 | echo "⚠️ DRY_RUN MODE ON ⚠️" 154 | DRY="echo" 155 | fi 156 | 157 | gh_echo "::group::⤵️ Fetching the NetBox source code" 158 | 159 | ### 160 | # Variables for fetching the NetBox source 161 | ### 162 | SRC_ORG="${SRC_ORG-netbox-community}" 163 | SRC_REPO="${SRC_REPO-netbox}" 164 | NETBOX_BRANCH="${1}" 165 | URL="${URL-https://github.com/${SRC_ORG}/${SRC_REPO}.git}" 166 | NETBOX_PATH="${NETBOX_PATH-.netbox}" 167 | 168 | ### 169 | # Fetching the NetBox source 170 | ### 171 | if [ "${2}" != "--push-only" ] && [ -z "${SKIP_GIT}" ]; then 172 | REMOTE_EXISTS=$(git ls-remote --heads --tags "${URL}" "${NETBOX_BRANCH}" | wc -l) 173 | if [ "${REMOTE_EXISTS}" == "0" ]; then 174 | echo "❌ Remote branch '${NETBOX_BRANCH}' not found in '${URL}'; Nothing to do" 175 | gh_out "skipped=true" 176 | exit 0 177 | fi 178 | echo "🌐 Checking out '${NETBOX_BRANCH}' of NetBox from the url '${URL}' into '${NETBOX_PATH}'" 179 | if [ ! -d "${NETBOX_PATH}" ]; then 180 | $DRY git clone -q --depth 10 -b "${NETBOX_BRANCH}" "${URL}" "${NETBOX_PATH}" 181 | fi 182 | 183 | ( 184 | $DRY cd "${NETBOX_PATH}" 185 | # shellcheck disable=SC2030 186 | if [ -n "${HTTP_PROXY}" ]; then 187 | git config http.proxy "${HTTP_PROXY}" 188 | fi 189 | 190 | $DRY git remote set-url origin "${URL}" 191 | $DRY git fetch -qp --depth 10 origin "${NETBOX_BRANCH}" 192 | $DRY git checkout -qf FETCH_HEAD 193 | $DRY git prune 194 | ) 195 | echo "✅ Checked out NetBox" 196 | fi 197 | 198 | gh_echo "::endgroup::" 199 | gh_echo "::group::🧮 Calculating Values" 200 | 201 | ### 202 | # Determining the value for DOCKERFILE 203 | # and checking whether it exists 204 | ### 205 | DOCKERFILE="${DOCKERFILE-Dockerfile}" 206 | if [ ! -f "${DOCKERFILE}" ]; then 207 | echo "🚨 The Dockerfile ${DOCKERFILE} doesn't exist." 208 | 209 | if [ -z "${DEBUG}" ]; then 210 | exit 1 211 | else 212 | echo "⚠️ Would exit here with code '1', but DEBUG is enabled." 213 | fi 214 | fi 215 | 216 | ### 217 | # Determining the value for DOCKER_FROM 218 | ### 219 | if [ -z "$DOCKER_FROM" ]; then 220 | DOCKER_FROM="docker.io/ubuntu:24.04" 221 | fi 222 | 223 | ### 224 | # Variables for labelling the docker image 225 | ### 226 | BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M+00:00')" 227 | 228 | if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then 229 | GIT_REF="$(git rev-parse HEAD)" 230 | fi 231 | 232 | # Read the project version from the `VERSION` file and trim it, see https://stackoverflow.com/a/3232433/172132 233 | PROJECT_VERSION="${PROJECT_VERSION-$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' VERSION)}" 234 | 235 | # Get the Git information from the netbox directory 236 | if [ -d "${NETBOX_PATH}/.git" ] && [ -z "${SKIP_GIT}" ]; then 237 | NETBOX_GIT_REF=$( 238 | cd "${NETBOX_PATH}" 239 | git rev-parse HEAD 240 | ) 241 | NETBOX_GIT_BRANCH=$( 242 | cd "${NETBOX_PATH}" 243 | git rev-parse --abbrev-ref HEAD 244 | ) 245 | NETBOX_GIT_URL=$( 246 | cd "${NETBOX_PATH}" 247 | git remote get-url origin 248 | ) 249 | fi 250 | 251 | ### 252 | # Variables for tagging the docker image 253 | ### 254 | DOCKER_REGISTRY="${DOCKER_REGISTRY-docker.io}" 255 | DOCKER_ORG="${DOCKER_ORG-netboxcommunity}" 256 | DOCKER_REPO="${DOCKER_REPO-netbox}" 257 | case "${NETBOX_BRANCH}" in 258 | main) 259 | TAG="${TAG-snapshot}" 260 | ;; 261 | *) 262 | TAG="${TAG-$NETBOX_BRANCH}" 263 | ;; 264 | esac 265 | 266 | ### 267 | # composing the final TARGET_DOCKER_TAG 268 | ### 269 | TARGET_DOCKER_TAG="${DOCKER_TAG-${TAG}}" 270 | TARGET_DOCKER_TAG_PROJECT="${TARGET_DOCKER_TAG}-${PROJECT_VERSION}" 271 | 272 | ### 273 | # composing the additional DOCKER_SHORT_TAG, 274 | # i.e. "v4.2.0" becomes "v4.2", 275 | # which is only relevant for version tags 276 | # Also let "latest" follow the highest version 277 | ### 278 | if [[ "${TAG}" =~ ^v([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then 279 | MAJOR=${BASH_REMATCH[1]} 280 | MINOR=${BASH_REMATCH[2]} 281 | 282 | TARGET_DOCKER_SHORT_TAG="${DOCKER_SHORT_TAG-v${MAJOR}.${MINOR}}" 283 | TARGET_DOCKER_LATEST_TAG="latest" 284 | TARGET_DOCKER_SHORT_TAG_PROJECT="${TARGET_DOCKER_SHORT_TAG}-${PROJECT_VERSION}" 285 | TARGET_DOCKER_LATEST_TAG_PROJECT="${TARGET_DOCKER_LATEST_TAG}-${PROJECT_VERSION}" 286 | fi 287 | 288 | IMAGE_NAME_TAGS=() 289 | for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do 290 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG}") 291 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_TAG_PROJECT}") 292 | done 293 | if [ -n "${TARGET_DOCKER_SHORT_TAG}" ]; then 294 | for IMAGE_NAME in "${IMAGE_NAMES[@]}"; do 295 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG}") 296 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_SHORT_TAG_PROJECT}") 297 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG}") 298 | IMAGE_NAME_TAGS+=("${IMAGE_NAME}:${TARGET_DOCKER_LATEST_TAG_PROJECT}") 299 | done 300 | fi 301 | 302 | FINAL_DOCKER_TAG="${IMAGE_NAME_TAGS[0]}" 303 | gh_env "FINAL_DOCKER_TAG=${IMAGE_NAME_TAGS[0]}" 304 | 305 | ### 306 | # Checking if the build is necessary, 307 | # meaning build only if one of those values changed: 308 | # - a new tag is beeing created 309 | # - base image digest 310 | # - netbox git ref (Label: netbox.git-ref) 311 | # - netbox-docker git ref (Label: org.opencontainers.image.revision) 312 | ### 313 | # Load information from registry (only for first registry in "IMAGE_NAMES") 314 | SHOULD_BUILD="false" 315 | BUILD_REASON="" 316 | if [ -z "${GH_ACTION}" ]; then 317 | # Asuming non Github builds should always proceed 318 | SHOULD_BUILD="true" 319 | BUILD_REASON="${BUILD_REASON} interactive" 320 | elif [ "false" == "$(check_if_tags_exists "${IMAGE_NAMES[0]}" "$TARGET_DOCKER_TAG")" ]; then 321 | SHOULD_BUILD="true" 322 | BUILD_REASON="${BUILD_REASON} newtag" 323 | else 324 | echo "Checking labels for '${FINAL_DOCKER_TAG}'" 325 | BASE_LAST_LAYER=$(get_image_last_layer "${DOCKER_FROM}") 326 | OLD_BASE_LAST_LAYER=$(get_image_label netbox.last-base-image-layer "${FINAL_DOCKER_TAG}") 327 | NETBOX_GIT_REF_OLD=$(get_image_label netbox.git-ref "${FINAL_DOCKER_TAG}") 328 | GIT_REF_OLD=$(get_image_label org.opencontainers.image.revision "${FINAL_DOCKER_TAG}") 329 | 330 | if [ "${BASE_LAST_LAYER}" != "${OLD_BASE_LAST_LAYER}" ]; then 331 | SHOULD_BUILD="true" 332 | BUILD_REASON="${BUILD_REASON} ubuntu" 333 | fi 334 | if [ "${NETBOX_GIT_REF}" != "${NETBOX_GIT_REF_OLD}" ]; then 335 | SHOULD_BUILD="true" 336 | BUILD_REASON="${BUILD_REASON} netbox" 337 | fi 338 | if [ "${GIT_REF}" != "${GIT_REF_OLD}" ]; then 339 | SHOULD_BUILD="true" 340 | BUILD_REASON="${BUILD_REASON} netbox-docker" 341 | fi 342 | fi 343 | 344 | if [ "${SHOULD_BUILD}" != "true" ]; then 345 | echo "Build skipped because sources didn't change" 346 | gh_out "skipped=true" 347 | exit 0 # Nothing to do -> exit 348 | else 349 | gh_out "skipped=false" 350 | fi 351 | gh_echo "::endgroup::" 352 | 353 | if [ "${CHECK_ONLY}" = "true" ]; then 354 | echo "Only check if build needed was requested. Exiting" 355 | exit 0 356 | fi 357 | 358 | ### 359 | # Build the image 360 | ### 361 | gh_echo "::group::🏗 Building the image" 362 | ### 363 | # Composing all arguments for `docker build` 364 | ### 365 | DOCKER_BUILD_ARGS=( 366 | --pull 367 | --target main 368 | -f "${DOCKERFILE}" 369 | ) 370 | for IMAGE_NAME in "${IMAGE_NAME_TAGS[@]}"; do 371 | DOCKER_BUILD_ARGS+=(-t "${IMAGE_NAME}") 372 | done 373 | 374 | # --label 375 | DOCKER_BUILD_ARGS+=( 376 | --label "netbox.original-tag=${TARGET_DOCKER_TAG_PROJECT}" 377 | --label "org.opencontainers.image.created=${BUILD_DATE}" 378 | --label "org.opencontainers.image.version=${PROJECT_VERSION}" 379 | ) 380 | if [ -d ".git" ] && [ -z "${SKIP_GIT}" ]; then 381 | DOCKER_BUILD_ARGS+=( 382 | --label "org.opencontainers.image.revision=${GIT_REF}" 383 | ) 384 | fi 385 | if [ -d "${NETBOX_PATH}/.git" ] && [ -z "${SKIP_GIT}" ]; then 386 | DOCKER_BUILD_ARGS+=( 387 | --label "netbox.git-branch=${NETBOX_GIT_BRANCH}" 388 | --label "netbox.git-ref=${NETBOX_GIT_REF}" 389 | --label "netbox.git-url=${NETBOX_GIT_URL}" 390 | ) 391 | fi 392 | if [ -n "${BUILD_REASON}" ]; then 393 | BUILD_REASON=$(sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' <<<"$BUILD_REASON") 394 | DOCKER_BUILD_ARGS+=(--label "netbox.build-reason=${BUILD_REASON}") 395 | DOCKER_BUILD_ARGS+=(--label "netbox.last-base-image-layer=${BASE_LAST_LAYER}") 396 | fi 397 | 398 | # --build-arg 399 | DOCKER_BUILD_ARGS+=(--build-arg "NETBOX_PATH=${NETBOX_PATH}") 400 | 401 | if [ -n "${DOCKER_FROM}" ]; then 402 | DOCKER_BUILD_ARGS+=(--build-arg "FROM=${DOCKER_FROM}") 403 | fi 404 | # shellcheck disable=SC2031 405 | if [ -n "${HTTP_PROXY}" ]; then 406 | DOCKER_BUILD_ARGS+=(--build-arg "http_proxy=${HTTP_PROXY}") 407 | DOCKER_BUILD_ARGS+=(--build-arg "https_proxy=${HTTPS_PROXY}") 408 | fi 409 | if [ -n "${NO_PROXY}" ]; then 410 | DOCKER_BUILD_ARGS+=(--build-arg "no_proxy=${NO_PROXY}") 411 | fi 412 | 413 | DOCKER_BUILD_ARGS+=(--platform "${BUILDX_PLATFORM-linux/amd64}") 414 | if [ "${2}" == "--push" ]; then 415 | # output type=docker does not work with pushing 416 | DOCKER_BUILD_ARGS+=( 417 | --output=type=image 418 | --push 419 | ) 420 | else 421 | DOCKER_BUILD_ARGS+=( 422 | --output=type=docker 423 | ) 424 | fi 425 | 426 | ### 427 | # Building the docker image 428 | ### 429 | if [ -z "${BUILDX_BUILDER_NAME}" ]; then 430 | BUILDX_BUILDER_NAME="$(basename "${PWD}")" 431 | fi 432 | if ! docker buildx ls | grep --quiet --word-regexp "${BUILDX_BUILDER_NAME}"; then 433 | echo "👷 Creating new Buildx Builder '${BUILDX_BUILDER_NAME}'" 434 | $DRY docker buildx create --name "${BUILDX_BUILDER_NAME}" 435 | BUILDX_BUILDER_CREATED="yes" 436 | fi 437 | 438 | echo "🐳 Building the Docker image '${TARGET_DOCKER_TAG_PROJECT}'." 439 | echo " Build reason set to: ${BUILD_REASON}" 440 | $DRY docker buildx \ 441 | --builder "${BUILDX_BUILDER_NAME}" \ 442 | build \ 443 | "${DOCKER_BUILD_ARGS[@]}" \ 444 | . 445 | echo "✅ Finished building the Docker images" 446 | gh_echo "::endgroup::" # End group for Build 447 | 448 | gh_echo "::group::🏗 Image Labels" 449 | echo "🔎 Inspecting labels on '${IMAGE_NAME_TAGS[0]}'" 450 | $DRY docker inspect "${IMAGE_NAME_TAGS[0]}" --format "{{json .Config.Labels}}" | jq 451 | gh_echo "::endgroup::" 452 | 453 | gh_echo "::group::🏗 Clean up" 454 | if [ -n "${BUILDX_REMOVE_BUILDER}" ] && [ "${BUILDX_BUILDER_CREATED}" == "yes" ]; then 455 | echo "👷 Removing Buildx Builder '${BUILDX_BUILDER_NAME}'" 456 | $DRY docker buildx rm "${BUILDX_BUILDER_NAME}" 457 | fi 458 | gh_echo "::endgroup::" 459 | -------------------------------------------------------------------------------- /configuration/configuration.py: -------------------------------------------------------------------------------- 1 | #### 2 | ## We recommend to not edit this file. 3 | ## Create separate files to overwrite the settings. 4 | ## See `extra.py` as an example. 5 | #### 6 | 7 | import re 8 | from os import environ 9 | from os.path import abspath, dirname, join 10 | from typing import Any, Callable, Tuple 11 | 12 | # For reference see https://docs.netbox.dev/en/stable/configuration/ 13 | # Based on https://github.com/netbox-community/netbox/blob/develop/netbox/netbox/configuration_example.py 14 | 15 | ### 16 | # NetBox-Docker Helper functions 17 | ### 18 | 19 | # Read secret from file 20 | def _read_secret(secret_name: str, default: str | None = None) -> str | None: 21 | try: 22 | f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8') 23 | except EnvironmentError: 24 | return default 25 | else: 26 | with f: 27 | return f.readline().strip() 28 | 29 | # If the `map_fn` isn't defined, then the value that is read from the environment (or the default value if not found) is returned. 30 | # If the `map_fn` is defined, then `map_fn` is invoked and the value (that was read from the environment or the default value if not found) 31 | # is passed to it as a parameter. The value returned from `map_fn` is then the return value of this function. 32 | # The `map_fn` is not invoked, if the value (that was read from the environment or the default value if not found) is None. 33 | def _environ_get_and_map(variable_name: str, default: str | None = None, map_fn: Callable[[str], Any | None] = None) -> Any | None: 34 | env_value = environ.get(variable_name, default) 35 | 36 | if env_value == None: 37 | return env_value 38 | 39 | if not map_fn: 40 | return env_value 41 | 42 | return map_fn(env_value) 43 | 44 | _AS_BOOL = lambda value : value.lower() == 'true' 45 | _AS_INT = lambda value : int(value) 46 | _AS_LIST = lambda value : list(filter(None, value.split(' '))) 47 | 48 | _BASE_DIR = dirname(dirname(abspath(__file__))) 49 | 50 | ######################### 51 | # # 52 | # Required settings # 53 | # # 54 | ######################### 55 | 56 | # This is a list of valid fully-qualified domain names (FQDNs) for the NetBox server. NetBox will not permit write 57 | # access to the server via any other hostnames. The first FQDN in the list will be treated as the preferred name. 58 | # 59 | # Example: ALLOWED_HOSTS = ['netbox.example.com', 'netbox.internal.local'] 60 | ALLOWED_HOSTS = environ.get('ALLOWED_HOSTS', '*').split(' ') 61 | # ensure that '*' or 'localhost' is always in ALLOWED_HOSTS (needed for health checks) 62 | if '*' not in ALLOWED_HOSTS and 'localhost' not in ALLOWED_HOSTS: 63 | ALLOWED_HOSTS.append('localhost') 64 | 65 | # PostgreSQL database configuration. See the Django documentation for a complete list of available parameters: 66 | # https://docs.djangoproject.com/en/stable/ref/settings/#databases 67 | DATABASE = { 68 | 'NAME': environ.get('DB_NAME', 'netbox'), # Database name 69 | 'USER': environ.get('DB_USER', ''), # PostgreSQL username 70 | 'PASSWORD': _read_secret('db_password', environ.get('DB_PASSWORD', '')), 71 | # PostgreSQL password 72 | 'HOST': environ.get('DB_HOST', 'localhost'), # Database server 73 | 'PORT': environ.get('DB_PORT', ''), # Database port (leave blank for default) 74 | 'OPTIONS': {'sslmode': environ.get('DB_SSLMODE', 'prefer')}, 75 | # Database connection SSLMODE 76 | 'CONN_MAX_AGE': _environ_get_and_map('DB_CONN_MAX_AGE', '300', _AS_INT), 77 | # Max database connection age 78 | 'DISABLE_SERVER_SIDE_CURSORS': _environ_get_and_map('DB_DISABLE_SERVER_SIDE_CURSORS', 'False', _AS_BOOL), 79 | # Disable the use of server-side cursors transaction pooling 80 | } 81 | 82 | # Redis database settings. Redis is used for caching and for queuing background tasks such as webhook events. A separate 83 | # configuration exists for each. Full connection details are required in both sections, and it is strongly recommended 84 | # to use two separate database IDs. 85 | REDIS = { 86 | 'tasks': { 87 | 'HOST': environ.get('REDIS_HOST', 'localhost'), 88 | 'PORT': _environ_get_and_map('REDIS_PORT', 6379, _AS_INT), 89 | 'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_SENTINELS', '', _AS_LIST) if uri != ''], 90 | 'SENTINEL_SERVICE': environ.get('REDIS_SENTINEL_SERVICE', 'default'), 91 | 'SENTINEL_TIMEOUT': _environ_get_and_map('REDIS_SENTINEL_TIMEOUT', 10, _AS_INT), 92 | 'USERNAME': environ.get('REDIS_USERNAME', ''), 93 | 'PASSWORD': _read_secret('redis_password', environ.get('REDIS_PASSWORD', '')), 94 | 'DATABASE': _environ_get_and_map('REDIS_DATABASE', 0, _AS_INT), 95 | 'SSL': _environ_get_and_map('REDIS_SSL', 'False', _AS_BOOL), 96 | 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False', _AS_BOOL), 97 | }, 98 | 'caching': { 99 | 'HOST': environ.get('REDIS_CACHE_HOST', environ.get('REDIS_HOST', 'localhost')), 100 | 'PORT': _environ_get_and_map('REDIS_CACHE_PORT', environ.get('REDIS_PORT', '6379'), _AS_INT), 101 | 'SENTINELS': [tuple(uri.split(':')) for uri in _environ_get_and_map('REDIS_CACHE_SENTINELS', '', _AS_LIST) if uri != ''], 102 | 'SENTINEL_SERVICE': environ.get('REDIS_CACHE_SENTINEL_SERVICE', environ.get('REDIS_SENTINEL_SERVICE', 'default')), 103 | 'USERNAME': environ.get('REDIS_CACHE_USERNAME', environ.get('REDIS_USERNAME', '')), 104 | 'PASSWORD': _read_secret('redis_cache_password', environ.get('REDIS_CACHE_PASSWORD', environ.get('REDIS_PASSWORD', ''))), 105 | 'DATABASE': _environ_get_and_map('REDIS_CACHE_DATABASE', '1', _AS_INT), 106 | 'SSL': _environ_get_and_map('REDIS_CACHE_SSL', environ.get('REDIS_SSL', 'False'), _AS_BOOL), 107 | 'INSECURE_SKIP_TLS_VERIFY': _environ_get_and_map('REDIS_CACHE_INSECURE_SKIP_TLS_VERIFY', environ.get('REDIS_INSECURE_SKIP_TLS_VERIFY', 'False'), _AS_BOOL), 108 | }, 109 | } 110 | 111 | # This key is used for secure generation of random numbers and strings. It must never be exposed outside of this file. 112 | # For optimal security, SECRET_KEY should be at least 50 characters in length and contain a mix of letters, numbers, and 113 | # symbols. NetBox will not run without this defined. For more information, see 114 | # https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-SECRET_KEY 115 | SECRET_KEY = _read_secret('secret_key', environ.get('SECRET_KEY', '')) 116 | 117 | 118 | ######################### 119 | # # 120 | # Optional settings # 121 | # # 122 | ######################### 123 | 124 | # # Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of 125 | # # application errors (assuming correct email settings are provided). 126 | # ADMINS = [ 127 | # # ['John Doe', 'jdoe@example.com'], 128 | # ] 129 | 130 | if 'ALLOWED_URL_SCHEMES' in environ: 131 | ALLOWED_URL_SCHEMES = _environ_get_and_map('ALLOWED_URL_SCHEMES', None, _AS_LIST) 132 | 133 | # Optionally display a persistent banner at the top and/or bottom of every page. HTML is allowed. To display the same 134 | # content in both banners, define BANNER_TOP and set BANNER_BOTTOM = BANNER_TOP. 135 | if 'BANNER_TOP' in environ: 136 | BANNER_TOP = environ.get('BANNER_TOP', None) 137 | if 'BANNER_BOTTOM' in environ: 138 | BANNER_BOTTOM = environ.get('BANNER_BOTTOM', None) 139 | 140 | # Text to include on the login page above the login form. HTML is allowed. 141 | if 'BANNER_LOGIN' in environ: 142 | BANNER_LOGIN = environ.get('BANNER_LOGIN', None) 143 | 144 | # Maximum number of days to retain logged changes. Set to 0 to retain changes indefinitely. (Default: 90) 145 | if 'CHANGELOG_RETENTION' in environ: 146 | CHANGELOG_RETENTION = _environ_get_and_map('CHANGELOG_RETENTION', None, _AS_INT) 147 | 148 | # Maximum number of days to retain job results (scripts and reports). Set to 0 to retain job results in the database indefinitely. (Default: 90) 149 | if 'JOB_RETENTION' in environ: 150 | JOB_RETENTION = _environ_get_and_map('JOB_RETENTION', None, _AS_INT) 151 | # JOBRESULT_RETENTION was renamed to JOB_RETENTION in the v3.5.0 release of NetBox. For backwards compatibility, map JOBRESULT_RETENTION to JOB_RETENTION 152 | elif 'JOBRESULT_RETENTION' in environ: 153 | JOB_RETENTION = _environ_get_and_map('JOBRESULT_RETENTION', None, _AS_INT) 154 | 155 | # API Cross-Origin Resource Sharing (CORS) settings. If CORS_ORIGIN_ALLOW_ALL is set to True, all origins will be 156 | # allowed. Otherwise, define a list of allowed origins using either CORS_ORIGIN_WHITELIST or 157 | # CORS_ORIGIN_REGEX_WHITELIST. For more information, see https://github.com/ottoyiu/django-cors-headers 158 | CORS_ORIGIN_ALLOW_ALL = _environ_get_and_map('CORS_ORIGIN_ALLOW_ALL', 'False', _AS_BOOL) 159 | CORS_ORIGIN_WHITELIST = _environ_get_and_map('CORS_ORIGIN_WHITELIST', 'https://localhost', _AS_LIST) 160 | CORS_ORIGIN_REGEX_WHITELIST = [re.compile(r) for r in _environ_get_and_map('CORS_ORIGIN_REGEX_WHITELIST', '', _AS_LIST)] 161 | 162 | # Set to True to enable server debugging. WARNING: Debugging introduces a substantial performance penalty and may reveal 163 | # sensitive information about your installation. Only enable debugging while performing testing. 164 | # Never enable debugging on a production system. 165 | DEBUG = _environ_get_and_map('DEBUG', 'False', _AS_BOOL) 166 | 167 | # This parameter serves as a safeguard to prevent some potentially dangerous behavior, 168 | # such as generating new database schema migrations. 169 | # Set this to True only if you are actively developing the NetBox code base. 170 | DEVELOPER = _environ_get_and_map('DEVELOPER', 'False', _AS_BOOL) 171 | 172 | # Email settings 173 | EMAIL = { 174 | 'SERVER': environ.get('EMAIL_SERVER', 'localhost'), 175 | 'PORT': _environ_get_and_map('EMAIL_PORT', 25, _AS_INT), 176 | 'USERNAME': environ.get('EMAIL_USERNAME', ''), 177 | 'PASSWORD': _read_secret('email_password', environ.get('EMAIL_PASSWORD', '')), 178 | 'USE_SSL': _environ_get_and_map('EMAIL_USE_SSL', 'False', _AS_BOOL), 179 | 'USE_TLS': _environ_get_and_map('EMAIL_USE_TLS', 'False', _AS_BOOL), 180 | 'SSL_CERTFILE': environ.get('EMAIL_SSL_CERTFILE', ''), 181 | 'SSL_KEYFILE': environ.get('EMAIL_SSL_KEYFILE', ''), 182 | 'TIMEOUT': _environ_get_and_map('EMAIL_TIMEOUT', 10, _AS_INT), # seconds 183 | 'FROM_EMAIL': environ.get('EMAIL_FROM', ''), 184 | } 185 | 186 | # Enforcement of unique IP space can be toggled on a per-VRF basis. To enforce unique IP space within the global table 187 | # (all prefixes and IP addresses not assigned to a VRF), set ENFORCE_GLOBAL_UNIQUE to True. 188 | if 'ENFORCE_GLOBAL_UNIQUE' in environ: 189 | ENFORCE_GLOBAL_UNIQUE = _environ_get_and_map('ENFORCE_GLOBAL_UNIQUE', None, _AS_BOOL) 190 | 191 | # By default, netbox sends census reporting data using a single HTTP request each time a worker starts. 192 | # This data enables the project maintainers to estimate how many NetBox deployments exist and track the adoption of new versions over time. 193 | # The only data reported by this function are the NetBox version, Python version, and a pseudorandom unique identifier. 194 | # To opt out of census reporting, set CENSUS_REPORTING_ENABLED to False. 195 | if 'CENSUS_REPORTING_ENABLED' in environ: 196 | CENSUS_REPORTING_ENABLED = _environ_get_and_map('CENSUS_REPORTING_ENABLED', None, _AS_BOOL) 197 | 198 | # Exempt certain models from the enforcement of view permissions. Models listed here will be viewable by all users and 199 | # by anonymous users. List models in the form `.`. Add '*' to this list to exempt all models. 200 | EXEMPT_VIEW_PERMISSIONS = _environ_get_and_map('EXEMPT_VIEW_PERMISSIONS', '', _AS_LIST) 201 | 202 | # HTTP proxies NetBox should use when sending outbound HTTP requests (e.g. for webhooks). 203 | HTTP_PROXIES = { 204 | 'http': environ.get('HTTP_PROXY', None), 205 | 'https': environ.get('HTTPS_PROXY', None), 206 | } 207 | 208 | # IP addresses recognized as internal to the system. The debugging toolbar will be available only to clients accessing 209 | # NetBox from an internal IP. 210 | INTERNAL_IPS = _environ_get_and_map('INTERNAL_IPS', '127.0.0.1 ::1', _AS_LIST) 211 | 212 | # Enable GraphQL API. 213 | if 'GRAPHQL_ENABLED' in environ: 214 | GRAPHQL_ENABLED = _environ_get_and_map('GRAPHQL_ENABLED', None, _AS_BOOL) 215 | 216 | # # Enable custom logging. Please see the Django documentation for detailed guidance on configuring custom logs: 217 | # # https://docs.djangoproject.com/en/stable/topics/logging/ 218 | # LOGGING = {} 219 | 220 | # Automatically reset the lifetime of a valid session upon each authenticated request. Enables users to remain 221 | # authenticated to NetBox indefinitely. 222 | LOGIN_PERSISTENCE = _environ_get_and_map('LOGIN_PERSISTENCE', 'False', _AS_BOOL) 223 | 224 | # When enabled, only authenticated users are permitted to access any part of NetBox. 225 | # Disabling this will allow unauthenticated users to access most areas of NetBox (but not make any changes). 226 | LOGIN_REQUIRED = _environ_get_and_map('LOGIN_REQUIRED', 'True', _AS_BOOL) 227 | 228 | # The length of time (in seconds) for which a user will remain logged into the web UI before being prompted to 229 | # re-authenticate. (Default: 1209600 [14 days]) 230 | LOGIN_TIMEOUT = _environ_get_and_map('LOGIN_TIMEOUT', 1209600, _AS_INT) 231 | 232 | # Setting this to True will display a "maintenance mode" banner at the top of every page. 233 | if 'MAINTENANCE_MODE' in environ: 234 | MAINTENANCE_MODE = _environ_get_and_map('MAINTENANCE_MODE', None, _AS_BOOL) 235 | 236 | # Maps provider 237 | if 'MAPS_URL' in environ: 238 | MAPS_URL = environ.get('MAPS_URL', None) 239 | 240 | # An API consumer can request an arbitrary number of objects =by appending the "limit" parameter to the URL (e.g. 241 | # "?limit=1000"). This setting defines the maximum limit. Setting it to 0 or None will allow an API consumer to request 242 | # all objects by specifying "?limit=0". 243 | if 'MAX_PAGE_SIZE' in environ: 244 | MAX_PAGE_SIZE = _environ_get_and_map('MAX_PAGE_SIZE', None, _AS_INT) 245 | 246 | # The file path where uploaded media such as image attachments are stored. A trailing slash is not needed. Note that 247 | # the default value of this setting is derived from the installed location. 248 | MEDIA_ROOT = environ.get('MEDIA_ROOT', join(_BASE_DIR, 'media')) 249 | 250 | # Expose Prometheus monitoring metrics at the HTTP endpoint '/metrics' 251 | METRICS_ENABLED = _environ_get_and_map('METRICS_ENABLED', 'False', _AS_BOOL) 252 | 253 | # Determine how many objects to display per page within a list. (Default: 50) 254 | if 'PAGINATE_COUNT' in environ: 255 | PAGINATE_COUNT = _environ_get_and_map('PAGINATE_COUNT', None, _AS_INT) 256 | 257 | # # Enable installed plugins. Add the name of each plugin to the list. 258 | # PLUGINS = [] 259 | 260 | # # Plugins configuration settings. These settings are used by various plugins that the user may have installed. 261 | # # Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. 262 | # PLUGINS_CONFIG = { 263 | # } 264 | 265 | # When determining the primary IP address for a device, IPv6 is preferred over IPv4 by default. Set this to True to 266 | # prefer IPv4 instead. 267 | if 'PREFER_IPV4' in environ: 268 | PREFER_IPV4 = _environ_get_and_map('PREFER_IPV4', None, _AS_BOOL) 269 | 270 | # The default value for the amperage field when creating new power feeds. 271 | if 'POWERFEED_DEFAULT_AMPERAGE' in environ: 272 | POWERFEED_DEFAULT_AMPERAGE = _environ_get_and_map('POWERFEED_DEFAULT_AMPERAGE', None, _AS_INT) 273 | 274 | # The default value (percentage) for the max_utilization field when creating new power feeds. 275 | if 'POWERFEED_DEFAULT_MAX_UTILIZATION' in environ: 276 | POWERFEED_DEFAULT_MAX_UTILIZATION = _environ_get_and_map('POWERFEED_DEFAULT_MAX_UTILIZATION', None, _AS_INT) 277 | 278 | # The default value for the voltage field when creating new power feeds. 279 | if 'POWERFEED_DEFAULT_VOLTAGE' in environ: 280 | POWERFEED_DEFAULT_VOLTAGE = _environ_get_and_map('POWERFEED_DEFAULT_VOLTAGE', None, _AS_INT) 281 | 282 | # Rack elevation size defaults, in pixels. For best results, the ratio of width to height should be roughly 10:1. 283 | if 'RACK_ELEVATION_DEFAULT_UNIT_HEIGHT' in environ: 284 | RACK_ELEVATION_DEFAULT_UNIT_HEIGHT = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_HEIGHT', None, _AS_INT) 285 | if 'RACK_ELEVATION_DEFAULT_UNIT_WIDTH' in environ: 286 | RACK_ELEVATION_DEFAULT_UNIT_WIDTH = _environ_get_and_map('RACK_ELEVATION_DEFAULT_UNIT_WIDTH', None, _AS_INT) 287 | 288 | # Remote authentication support 289 | REMOTE_AUTH_AUTO_CREATE_GROUPS = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_GROUPS', 'False', _AS_BOOL) 290 | REMOTE_AUTH_AUTO_CREATE_USER = _environ_get_and_map('REMOTE_AUTH_AUTO_CREATE_USER', 'False', _AS_BOOL) 291 | REMOTE_AUTH_BACKEND = _environ_get_and_map('REMOTE_AUTH_BACKEND', 'netbox.authentication.RemoteUserBackend', _AS_LIST) 292 | REMOTE_AUTH_DEFAULT_GROUPS = _environ_get_and_map('REMOTE_AUTH_DEFAULT_GROUPS', '', _AS_LIST) 293 | # REMOTE_AUTH_DEFAULT_PERMISSIONS = {} # dicts can't be configured via environment variables. See extra.py instead. 294 | REMOTE_AUTH_ENABLED = _environ_get_and_map('REMOTE_AUTH_ENABLED', 'False', _AS_BOOL) 295 | REMOTE_AUTH_GROUP_HEADER = _environ_get_and_map('REMOTE_AUTH_GROUP_HEADER', 'HTTP_REMOTE_USER_GROUP') 296 | REMOTE_AUTH_GROUP_SEPARATOR = _environ_get_and_map('REMOTE_AUTH_GROUP_SEPARATOR', '|') 297 | REMOTE_AUTH_GROUP_SYNC_ENABLED = _environ_get_and_map('REMOTE_AUTH_GROUP_SYNC_ENABLED', 'False', _AS_BOOL) 298 | REMOTE_AUTH_HEADER = environ.get('REMOTE_AUTH_HEADER', 'HTTP_REMOTE_USER') 299 | REMOTE_AUTH_USER_EMAIL = environ.get('REMOTE_AUTH_USER_EMAIL', 'HTTP_REMOTE_USER_EMAIL') 300 | REMOTE_AUTH_USER_FIRST_NAME = environ.get('REMOTE_AUTH_USER_FIRST_NAME', 'HTTP_REMOTE_USER_FIRST_NAME') 301 | REMOTE_AUTH_USER_LAST_NAME = environ.get('REMOTE_AUTH_USER_LAST_NAME', 'HTTP_REMOTE_USER_LAST_NAME') 302 | REMOTE_AUTH_SUPERUSER_GROUPS = _environ_get_and_map('REMOTE_AUTH_SUPERUSER_GROUPS', '', _AS_LIST) 303 | REMOTE_AUTH_SUPERUSERS = _environ_get_and_map('REMOTE_AUTH_SUPERUSERS', '', _AS_LIST) 304 | REMOTE_AUTH_STAFF_GROUPS = _environ_get_and_map('REMOTE_AUTH_STAFF_GROUPS', '', _AS_LIST) 305 | REMOTE_AUTH_STAFF_USERS = _environ_get_and_map('REMOTE_AUTH_STAFF_USERS', '', _AS_LIST) 306 | 307 | # This repository is used to check whether there is a new release of NetBox available. Set to None to disable the 308 | # version check or use the URL below to check for release in the official NetBox repository. 309 | RELEASE_CHECK_URL = environ.get('RELEASE_CHECK_URL', None) 310 | # RELEASE_CHECK_URL = 'https://api.github.com/repos/netbox-community/netbox/releases' 311 | 312 | # Maximum execution time for background tasks, in seconds. 313 | RQ_DEFAULT_TIMEOUT = _environ_get_and_map('RQ_DEFAULT_TIMEOUT', 300, _AS_INT) 314 | 315 | # The name to use for the csrf token cookie. 316 | CSRF_COOKIE_NAME = environ.get('CSRF_COOKIE_NAME', 'csrftoken') 317 | 318 | # Cross-Site-Request-Forgery-Attack settings. If Netbox is sitting behind a reverse proxy, you might need to set the CSRF_TRUSTED_ORIGINS flag. 319 | # Django 4.0 requires to specify the URL Scheme in this setting. An example environment variable could be specified like: 320 | # CSRF_TRUSTED_ORIGINS=https://demo.netbox.dev http://demo.netbox.dev 321 | CSRF_TRUSTED_ORIGINS = _environ_get_and_map('CSRF_TRUSTED_ORIGINS', '', _AS_LIST) 322 | 323 | # The name to use for the session cookie. 324 | SESSION_COOKIE_NAME = environ.get('SESSION_COOKIE_NAME', 'sessionid') 325 | 326 | # If true, the `includeSubDomains` directive will be included in the HTTP Strict Transport Security (HSTS) header. 327 | # This directive instructs the browser to apply the HSTS policy to all subdomains of the current domain. 328 | SECURE_HSTS_INCLUDE_SUBDOMAINS = _environ_get_and_map('SECURE_HSTS_INCLUDE_SUBDOMAINS', 'False', _AS_BOOL) 329 | 330 | # If true, the `preload` directive will be included in the HTTP Strict Transport Security (HSTS) header. 331 | # This directive instructs the browser to preload the site in HTTPS. Browsers that use the HSTS preload list will force the 332 | # site to be accessed via HTTPS even if the user types HTTP in the address bar. 333 | SECURE_HSTS_PRELOAD = _environ_get_and_map('SECURE_HSTS_PRELOAD', 'False', _AS_BOOL) 334 | 335 | # If set to a non-zero integer value, the SecurityMiddleware sets the HTTP Strict Transport Security (HSTS) header on all 336 | # responses that do not already have it. This will instruct the browser that the website must be accessed via HTTPS, 337 | # blocking any HTTP request. 338 | SECURE_HSTS_SECONDS = _environ_get_and_map('SECURE_HSTS_SECONDS', 0, _AS_INT) 339 | 340 | # If true, all non-HTTPS requests will be automatically redirected to use HTTPS. 341 | SECURE_SSL_REDIRECT = _environ_get_and_map('SECURE_SSL_REDIRECT', 'False', _AS_BOOL) 342 | 343 | # By default, NetBox will store session data in the database. Alternatively, a file path can be specified here to use 344 | # local file storage instead. (This can be useful for enabling authentication on a standby instance with read-only 345 | # database access.) Note that the user as which NetBox runs must have read and write permissions to this path. 346 | SESSION_FILE_PATH = environ.get('SESSION_FILE_PATH', environ.get('SESSIONS_ROOT', None)) 347 | 348 | # Time zone (default: UTC) 349 | TIME_ZONE = environ.get('TIME_ZONE', 'UTC') 350 | 351 | # If true disables miscellaneous functionality which depends on access to the Internet. 352 | ISOLATED_DEPLOYMENT = _environ_get_and_map('ISOLATED_DEPLOYMENT', 'False', _AS_BOOL) 353 | -------------------------------------------------------------------------------- /configuration/extra.py: -------------------------------------------------------------------------------- 1 | #### 2 | ## This file contains extra configuration options that can't be configured 3 | ## directly through environment variables. 4 | #### 5 | 6 | ## Specify one or more name and email address tuples representing NetBox administrators. These people will be notified of 7 | ## application errors (assuming correct email settings are provided). 8 | # ADMINS = [ 9 | # # ['John Doe', 'jdoe@example.com'], 10 | # ] 11 | 12 | 13 | ## URL schemes that are allowed within links in NetBox 14 | # ALLOWED_URL_SCHEMES = ( 15 | # 'file', 'ftp', 'ftps', 'http', 'https', 'irc', 'mailto', 'sftp', 'ssh', 'tel', 'telnet', 'tftp', 'vnc', 'xmpp', 16 | # ) 17 | 18 | ## Enable installed plugins. Add the name of each plugin to the list. 19 | # from netbox.configuration.configuration import PLUGINS 20 | # PLUGINS.append('my_plugin') 21 | 22 | ## Plugins configuration settings. These settings are used by various plugins that the user may have installed. 23 | ## Each key in the dictionary is the name of an installed plugin and its value is a dictionary of settings. 24 | # from netbox.configuration.configuration import PLUGINS_CONFIG 25 | # PLUGINS_CONFIG['my_plugin'] = { 26 | # 'foo': 'bar', 27 | # 'buzz': 'bazz' 28 | # } 29 | 30 | 31 | ## Remote authentication support 32 | # REMOTE_AUTH_DEFAULT_PERMISSIONS = {} 33 | 34 | 35 | ## By default uploaded media is stored on the local filesystem. Using Django-storages is also supported. Provide the 36 | ## class path of the storage driver in STORAGE_BACKEND and any configuration options in STORAGE_CONFIG. For example: 37 | # STORAGE_BACKEND = 'storages.backends.s3boto3.S3Boto3Storage' 38 | # STORAGE_CONFIG = { 39 | # 'AWS_ACCESS_KEY_ID': 'Key ID', 40 | # 'AWS_SECRET_ACCESS_KEY': 'Secret', 41 | # 'AWS_STORAGE_BUCKET_NAME': 'netbox', 42 | # 'AWS_S3_REGION_NAME': 'eu-west-1', 43 | # } 44 | 45 | 46 | ## This file can contain arbitrary Python code, e.g.: 47 | # from datetime import datetime 48 | # now = datetime.now().strftime("%d/%m/%Y %H:%M:%S") 49 | # BANNER_TOP = f'This instance started on {now}.' 50 | -------------------------------------------------------------------------------- /configuration/ldap/extra.py: -------------------------------------------------------------------------------- 1 | #### 2 | ## This file contains extra configuration options that can't be configured 3 | ## directly through environment variables. 4 | ## All vairables set here overwrite any existing found in ldap_config.py 5 | #### 6 | 7 | # # This Python script inherits all the imports from ldap_config.py 8 | # from django_auth_ldap.config import LDAPGroupQuery # Imported since not in ldap_config.py 9 | 10 | # # Sets a base requirement of membetship to netbox-user-ro, netbox-user-rw, or netbox-user-admin. 11 | # AUTH_LDAP_REQUIRE_GROUP = ( 12 | # LDAPGroupQuery("cn=netbox-user-ro,ou=groups,dc=example,dc=com") 13 | # | LDAPGroupQuery("cn=netbox-user-rw,ou=groups,dc=example,dc=com") 14 | # | LDAPGroupQuery("cn=netbox-user-admin,ou=groups,dc=example,dc=com") 15 | # ) 16 | 17 | # # Sets LDAP Flag groups variables with example. 18 | # AUTH_LDAP_USER_FLAGS_BY_GROUP = { 19 | # "is_staff": ( 20 | # LDAPGroupQuery("cn=netbox-user-ro,ou=groups,dc=example,dc=com") 21 | # | LDAPGroupQuery("cn=netbox-user-rw,ou=groups,dc=example,dc=com") 22 | # | LDAPGroupQuery("cn=netbox-user-admin,ou=groups,dc=example,dc=com") 23 | # ), 24 | # "is_superuser": "cn=netbox-user-admin,ou=groups,dc=example,dc=com", 25 | # } 26 | 27 | # # Sets LDAP Mirror groups variables with example groups 28 | # AUTH_LDAP_MIRROR_GROUPS = ["netbox-user-ro", "netbox-user-rw", "netbox-user-admin"] 29 | -------------------------------------------------------------------------------- /configuration/ldap/ldap_config.py: -------------------------------------------------------------------------------- 1 | from importlib import import_module 2 | from os import environ 3 | 4 | import ldap 5 | from django_auth_ldap.config import LDAPSearch 6 | 7 | 8 | # Read secret from file 9 | def _read_secret(secret_name, default=None): 10 | try: 11 | f = open('/run/secrets/' + secret_name, 'r', encoding='utf-8') 12 | except EnvironmentError: 13 | return default 14 | else: 15 | with f: 16 | return f.readline().strip() 17 | 18 | # Import and return the group type based on string name 19 | def _import_group_type(group_type_name): 20 | mod = import_module('django_auth_ldap.config') 21 | try: 22 | return getattr(mod, group_type_name)() 23 | except: 24 | return None 25 | 26 | # Server URI 27 | AUTH_LDAP_SERVER_URI = environ.get('AUTH_LDAP_SERVER_URI', '') 28 | 29 | # The following may be needed if you are binding to Active Directory. 30 | AUTH_LDAP_CONNECTION_OPTIONS = { 31 | ldap.OPT_REFERRALS: 0 32 | } 33 | 34 | AUTH_LDAP_BIND_AS_AUTHENTICATING_USER = environ.get('AUTH_LDAP_BIND_AS_AUTHENTICATING_USER', 'False').lower() == 'true' 35 | 36 | # Set the DN and password for the NetBox service account if needed. 37 | if not AUTH_LDAP_BIND_AS_AUTHENTICATING_USER: 38 | AUTH_LDAP_BIND_DN = environ.get('AUTH_LDAP_BIND_DN', '') 39 | AUTH_LDAP_BIND_PASSWORD = _read_secret('auth_ldap_bind_password', environ.get('AUTH_LDAP_BIND_PASSWORD', '')) 40 | 41 | # Set a string template that describes any user’s distinguished name based on the username. 42 | AUTH_LDAP_USER_DN_TEMPLATE = environ.get('AUTH_LDAP_USER_DN_TEMPLATE', None) 43 | 44 | # Enable STARTTLS for ldap authentication. 45 | AUTH_LDAP_START_TLS = environ.get('AUTH_LDAP_START_TLS', 'False').lower() == 'true' 46 | 47 | # Include this setting if you want to ignore certificate errors. This might be needed to accept a self-signed cert. 48 | # Note that this is a NetBox-specific setting which sets: 49 | # ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) 50 | LDAP_IGNORE_CERT_ERRORS = environ.get('LDAP_IGNORE_CERT_ERRORS', 'False').lower() == 'true' 51 | 52 | # Include this setting if you want to validate the LDAP server certificates against a CA certificate directory on your server 53 | # Note that this is a NetBox-specific setting which sets: 54 | # ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, LDAP_CA_CERT_DIR) 55 | LDAP_CA_CERT_DIR = environ.get('LDAP_CA_CERT_DIR', None) 56 | 57 | # Include this setting if you want to validate the LDAP server certificates against your own CA. 58 | # Note that this is a NetBox-specific setting which sets: 59 | # ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, LDAP_CA_CERT_FILE) 60 | LDAP_CA_CERT_FILE = environ.get('LDAP_CA_CERT_FILE', None) 61 | 62 | AUTH_LDAP_USER_SEARCH_BASEDN = environ.get('AUTH_LDAP_USER_SEARCH_BASEDN', '') 63 | AUTH_LDAP_USER_SEARCH_ATTR = environ.get('AUTH_LDAP_USER_SEARCH_ATTR', 'sAMAccountName') 64 | AUTH_LDAP_USER_SEARCH_FILTER: str = environ.get( 65 | 'AUTH_LDAP_USER_SEARCH_FILTER', f'({AUTH_LDAP_USER_SEARCH_ATTR}=%(user)s)' 66 | ) 67 | 68 | AUTH_LDAP_USER_SEARCH = LDAPSearch( 69 | AUTH_LDAP_USER_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_USER_SEARCH_FILTER 70 | ) 71 | 72 | # This search ought to return all groups to which the user belongs. django_auth_ldap uses this to determine group 73 | # heirarchy. 74 | 75 | AUTH_LDAP_GROUP_SEARCH_BASEDN = environ.get('AUTH_LDAP_GROUP_SEARCH_BASEDN', '') 76 | AUTH_LDAP_GROUP_SEARCH_CLASS = environ.get('AUTH_LDAP_GROUP_SEARCH_CLASS', 'group') 77 | 78 | AUTH_LDAP_GROUP_SEARCH_FILTER: str = environ.get( 79 | 'AUTH_LDAP_GROUP_SEARCH_FILTER', f'(objectclass={AUTH_LDAP_GROUP_SEARCH_CLASS})' 80 | ) 81 | AUTH_LDAP_GROUP_SEARCH = LDAPSearch( 82 | AUTH_LDAP_GROUP_SEARCH_BASEDN, ldap.SCOPE_SUBTREE, AUTH_LDAP_GROUP_SEARCH_FILTER 83 | ) 84 | AUTH_LDAP_GROUP_TYPE = _import_group_type(environ.get('AUTH_LDAP_GROUP_TYPE', 'GroupOfNamesType')) 85 | 86 | # Define a group required to login. 87 | AUTH_LDAP_REQUIRE_GROUP = environ.get('AUTH_LDAP_REQUIRE_GROUP_DN') 88 | 89 | # Define special user types using groups. Exercise great caution when assigning superuser status. 90 | AUTH_LDAP_USER_FLAGS_BY_GROUP = {} 91 | 92 | if AUTH_LDAP_REQUIRE_GROUP is not None: 93 | AUTH_LDAP_USER_FLAGS_BY_GROUP = { 94 | "is_active": environ.get('AUTH_LDAP_REQUIRE_GROUP_DN', ''), 95 | "is_staff": environ.get('AUTH_LDAP_IS_ADMIN_DN', ''), 96 | "is_superuser": environ.get('AUTH_LDAP_IS_SUPERUSER_DN', '') 97 | } 98 | 99 | # For more granular permissions, we can map LDAP groups to Django groups. 100 | AUTH_LDAP_FIND_GROUP_PERMS = environ.get('AUTH_LDAP_FIND_GROUP_PERMS', 'True').lower() == 'true' 101 | AUTH_LDAP_MIRROR_GROUPS = environ.get('AUTH_LDAP_MIRROR_GROUPS', '').lower() == 'true' 102 | 103 | # Cache groups for one hour to reduce LDAP traffic 104 | AUTH_LDAP_CACHE_TIMEOUT = int(environ.get('AUTH_LDAP_CACHE_TIMEOUT', 3600)) 105 | 106 | # Populate the Django user from the LDAP directory. 107 | AUTH_LDAP_USER_ATTR_MAP = { 108 | "first_name": environ.get('AUTH_LDAP_ATTR_FIRSTNAME', 'givenName'), 109 | "last_name": environ.get('AUTH_LDAP_ATTR_LASTNAME', 'sn'), 110 | "email": environ.get('AUTH_LDAP_ATTR_MAIL', 'mail') 111 | } 112 | -------------------------------------------------------------------------------- /configuration/logging.py: -------------------------------------------------------------------------------- 1 | # # Remove first comment(#) on each line to implement this working logging example. 2 | # # Add LOGLEVEL environment variable to netbox if you use this example & want a different log level. 3 | # from os import environ 4 | 5 | # # Set LOGLEVEL in netbox.env or docker-compose.overide.yml to override a logging level of INFO. 6 | # LOGLEVEL = environ.get('LOGLEVEL', 'INFO') 7 | 8 | # LOGGING = { 9 | 10 | # 'version': 1, 11 | # 'disable_existing_loggers': False, 12 | # 'formatters': { 13 | # 'verbose': { 14 | # 'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}', 15 | # 'style': '{', 16 | # }, 17 | # 'simple': { 18 | # 'format': '{levelname} {message}', 19 | # 'style': '{', 20 | # }, 21 | # }, 22 | # 'filters': { 23 | # 'require_debug_false': { 24 | # '()': 'django.utils.log.RequireDebugFalse', 25 | # }, 26 | # }, 27 | # 'handlers': { 28 | # 'console': { 29 | # 'level': LOGLEVEL, 30 | # 'filters': ['require_debug_false'], 31 | # 'class': 'logging.StreamHandler', 32 | # 'formatter': 'simple' 33 | # }, 34 | # 'mail_admins': { 35 | # 'level': 'ERROR', 36 | # 'class': 'django.utils.log.AdminEmailHandler', 37 | # 'filters': ['require_debug_false'] 38 | # } 39 | # }, 40 | # 'loggers': { 41 | # 'django': { 42 | # 'handlers': ['console'], 43 | # 'propagate': True, 44 | # }, 45 | # 'django.request': { 46 | # 'handlers': ['mail_admins'], 47 | # 'level': 'ERROR', 48 | # 'propagate': False, 49 | # }, 50 | # 'django_auth_ldap': { 51 | # 'handlers': ['console',], 52 | # 'level': LOGLEVEL, 53 | # } 54 | # } 55 | # } 56 | -------------------------------------------------------------------------------- /configuration/plugins.py: -------------------------------------------------------------------------------- 1 | # Add your plugins and plugin settings here. 2 | # Of course uncomment this file out. 3 | 4 | # To learn how to build images with your required plugins 5 | # See https://github.com/netbox-community/netbox-docker/wiki/Using-Netbox-Plugins 6 | 7 | # PLUGINS = ["netbox_bgp"] 8 | 9 | # PLUGINS_CONFIG = { 10 | # "netbox_bgp": { 11 | # ADD YOUR SETTINGS HERE 12 | # } 13 | # } 14 | -------------------------------------------------------------------------------- /docker-compose.override.yml.example: -------------------------------------------------------------------------------- 1 | services: 2 | netbox: 3 | ports: 4 | - "8000:8080" 5 | # If you want the Nginx unit status page visible from the 6 | # outside of the container add the following port mapping: 7 | # - "8001:8081" 8 | # healthcheck: 9 | # Time for which the health check can fail after the container is started. 10 | # This depends mostly on the performance of your database. On the first start, 11 | # when all tables need to be created the start_period should be higher than on 12 | # subsequent starts. For the first start after major version upgrades of NetBox 13 | # the start_period might also need to be set higher. 14 | # Default value in our docker-compose.yml is 60s 15 | # start_period: 90s 16 | # environment: 17 | # SKIP_SUPERUSER: "false" 18 | # SUPERUSER_API_TOKEN: "" 19 | # SUPERUSER_EMAIL: "" 20 | # SUPERUSER_NAME: "" 21 | # SUPERUSER_PASSWORD: "" 22 | 23 | -------------------------------------------------------------------------------- /docker-compose.test.override.yml: -------------------------------------------------------------------------------- 1 | services: 2 | netbox: 3 | ports: 4 | - "127.0.0.1:8000:8080" 5 | 6 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | services: 2 | netbox: &netbox 3 | image: ${IMAGE-docker.io/netboxcommunity/netbox:latest} 4 | depends_on: 5 | postgres: 6 | condition: service_healthy 7 | redis: 8 | condition: service_healthy 9 | redis-cache: 10 | condition: service_healthy 11 | env_file: env/netbox.env 12 | user: "unit:root" 13 | volumes: 14 | - ./test-configuration/test_config.py:/etc/netbox/config/test_config.py:z,ro 15 | healthcheck: 16 | test: curl -f http://localhost:8080/login/ || exit 1 17 | start_period: ${NETBOX_START_PERIOD-120s} 18 | timeout: 3s 19 | interval: 15s 20 | netbox-worker: 21 | <<: *netbox 22 | command: 23 | - /opt/netbox/venv/bin/python 24 | - /opt/netbox/netbox/manage.py 25 | - rqworker 26 | healthcheck: 27 | test: ps -aux | grep -v grep | grep -q rqworker || exit 1 28 | start_period: 40s 29 | timeout: 3s 30 | interval: 15s 31 | netbox-housekeeping: 32 | <<: *netbox 33 | command: 34 | - /opt/netbox/housekeeping.sh 35 | healthcheck: 36 | test: ps -aux | grep -v grep | grep -q housekeeping || exit 1 37 | start_period: 40s 38 | timeout: 3s 39 | interval: 15s 40 | 41 | postgres: 42 | image: docker.io/postgres:17-alpine 43 | env_file: env/postgres.env 44 | healthcheck: 45 | test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER ## $$ because of docker-compose 46 | start_period: 20s 47 | interval: 1s 48 | timeout: 5s 49 | retries: 5 50 | 51 | redis: &redis 52 | image: docker.io/valkey/valkey:8.1-alpine 53 | command: 54 | - sh 55 | - -c # this is to evaluate the $REDIS_PASSWORD from the env 56 | - valkey-server --save "" --appendonly no --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose 57 | env_file: env/redis.env 58 | healthcheck: 59 | test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]' 60 | start_period: 5s 61 | timeout: 3s 62 | interval: 1s 63 | retries: 5 64 | redis-cache: 65 | <<: *redis 66 | env_file: env/redis-cache.env 67 | 68 | volumes: 69 | netbox-media-files: 70 | driver: local 71 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | netbox: &netbox 3 | image: docker.io/netboxcommunity/netbox:${VERSION-v4.3-3.3.0} 4 | depends_on: 5 | - postgres 6 | - redis 7 | - redis-cache 8 | env_file: env/netbox.env 9 | user: "unit:root" 10 | healthcheck: 11 | test: curl -f http://localhost:8080/login/ || exit 1 12 | start_period: 90s 13 | timeout: 3s 14 | interval: 15s 15 | volumes: 16 | - ./configuration:/etc/netbox/config:z,ro 17 | - netbox-media-files:/opt/netbox/netbox/media:rw 18 | - netbox-reports-files:/opt/netbox/netbox/reports:rw 19 | - netbox-scripts-files:/opt/netbox/netbox/scripts:rw 20 | netbox-worker: 21 | <<: *netbox 22 | depends_on: 23 | netbox: 24 | condition: service_healthy 25 | command: 26 | - /opt/netbox/venv/bin/python 27 | - /opt/netbox/netbox/manage.py 28 | - rqworker 29 | healthcheck: 30 | test: ps -aux | grep -v grep | grep -q rqworker || exit 1 31 | start_period: 20s 32 | timeout: 3s 33 | interval: 15s 34 | netbox-housekeeping: 35 | <<: *netbox 36 | depends_on: 37 | netbox: 38 | condition: service_healthy 39 | command: 40 | - /opt/netbox/housekeeping.sh 41 | healthcheck: 42 | test: ps -aux | grep -v grep | grep -q housekeeping || exit 1 43 | start_period: 20s 44 | timeout: 3s 45 | interval: 15s 46 | 47 | # postgres 48 | postgres: 49 | image: docker.io/postgres:17-alpine 50 | healthcheck: 51 | test: pg_isready -q -t 2 -d $$POSTGRES_DB -U $$POSTGRES_USER 52 | start_period: 20s 53 | timeout: 30s 54 | interval: 10s 55 | retries: 5 56 | env_file: env/postgres.env 57 | volumes: 58 | - netbox-postgres-data:/var/lib/postgresql/data 59 | 60 | # redis 61 | redis: 62 | image: docker.io/valkey/valkey:8.1-alpine 63 | command: 64 | - sh 65 | - -c # this is to evaluate the $REDIS_PASSWORD from the env 66 | - valkey-server --appendonly yes --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose 67 | healthcheck: &redis-healthcheck 68 | test: '[ $$(valkey-cli --pass "$${REDIS_PASSWORD}" ping) = ''PONG'' ]' 69 | start_period: 5s 70 | timeout: 3s 71 | interval: 1s 72 | retries: 5 73 | env_file: env/redis.env 74 | volumes: 75 | - netbox-redis-data:/data 76 | redis-cache: 77 | image: docker.io/valkey/valkey:8.1-alpine 78 | command: 79 | - sh 80 | - -c # this is to evaluate the $REDIS_PASSWORD from the env 81 | - valkey-server --requirepass $$REDIS_PASSWORD ## $$ because of docker-compose 82 | healthcheck: *redis-healthcheck 83 | env_file: env/redis-cache.env 84 | volumes: 85 | - netbox-redis-cache-data:/data 86 | 87 | volumes: 88 | netbox-media-files: 89 | driver: local 90 | netbox-postgres-data: 91 | driver: local 92 | netbox-redis-cache-data: 93 | driver: local 94 | netbox-redis-data: 95 | driver: local 96 | netbox-reports-files: 97 | driver: local 98 | netbox-scripts-files: 99 | driver: local 100 | -------------------------------------------------------------------------------- /docker/configuration.docker.py: -------------------------------------------------------------------------------- 1 | ## Generic Parts 2 | # These functions are providing the functionality to load 3 | # arbitrary configuration files. 4 | # 5 | # They can be imported by other code (see `ldap_config.py` for an example). 6 | 7 | import importlib.util 8 | import sys 9 | from os import scandir 10 | from os.path import abspath, isfile 11 | 12 | 13 | def _filename(f): 14 | return f.name 15 | 16 | 17 | def _import(module_name, path, loaded_configurations): 18 | spec = importlib.util.spec_from_file_location("", path) 19 | module = importlib.util.module_from_spec(spec) 20 | spec.loader.exec_module(module) 21 | sys.modules[module_name] = module 22 | 23 | loaded_configurations.insert(0, module) 24 | 25 | print(f"🧬 loaded config '{path}'") 26 | 27 | 28 | def read_configurations(config_module, config_dir, main_config): 29 | loaded_configurations = [] 30 | 31 | main_config_path = abspath(f"{config_dir}/{main_config}.py") 32 | if isfile(main_config_path): 33 | _import(f"{config_module}.{main_config}", main_config_path, loaded_configurations) 34 | else: 35 | print(f"⚠️ Main configuration '{main_config_path}' not found.") 36 | 37 | with scandir(config_dir) as it: 38 | for f in sorted(it, key=_filename): 39 | if not f.is_file(): 40 | continue 41 | 42 | if f.name.startswith("__"): 43 | continue 44 | 45 | if not f.name.endswith(".py"): 46 | continue 47 | 48 | if f.name == f"{main_config}.py": 49 | continue 50 | 51 | if f.name == f"{config_dir}.py": 52 | continue 53 | 54 | module_name = f"{config_module}.{f.name[:-len('.py')]}".replace(".", "_") 55 | _import(module_name, f.path, loaded_configurations) 56 | 57 | if len(loaded_configurations) == 0: 58 | print(f"‼️ No configuration files found in '{config_dir}'.") 59 | raise ImportError(f"No configuration files found in '{config_dir}'.") 60 | 61 | return loaded_configurations 62 | 63 | 64 | ## Specific Parts 65 | # This section's code actually loads the various configuration files 66 | # into the module with the given name. 67 | # It contains the logic to resolve arbitrary configuration options by 68 | # levaraging dynamic programming using `__getattr__`. 69 | 70 | 71 | _loaded_configurations = read_configurations( 72 | config_dir="/etc/netbox/config/", 73 | config_module="netbox.configuration", 74 | main_config="configuration", 75 | ) 76 | 77 | 78 | def __getattr__(name): 79 | for config in _loaded_configurations: 80 | try: 81 | return getattr(config, name) 82 | except: 83 | pass 84 | raise AttributeError 85 | 86 | 87 | def __dir__(): 88 | names = [] 89 | for config in _loaded_configurations: 90 | names.extend(config.__dir__()) 91 | return names 92 | -------------------------------------------------------------------------------- /docker/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Runs on every start of the NetBox Docker container 3 | 4 | # Stop when an error occures 5 | set -e 6 | 7 | # Allows NetBox to be run as non-root users 8 | umask 002 9 | 10 | # Load correct Python3 env 11 | # shellcheck disable=SC1091 12 | source /opt/netbox/venv/bin/activate 13 | 14 | # Try to connect to the DB 15 | DB_WAIT_TIMEOUT=${DB_WAIT_TIMEOUT-3} 16 | MAX_DB_WAIT_TIME=${MAX_DB_WAIT_TIME-30} 17 | CUR_DB_WAIT_TIME=0 18 | while [ "${CUR_DB_WAIT_TIME}" -lt "${MAX_DB_WAIT_TIME}" ]; do 19 | # Read and truncate connection error tracebacks to last line by default 20 | exec {psfd}< <(./manage.py showmigrations 2>&1) 21 | read -rd '' DB_ERR <&$psfd || : 22 | exec {psfd}<&- 23 | wait $! && break 24 | if [ -n "$DB_WAIT_DEBUG" ]; then 25 | echo "$DB_ERR" 26 | else 27 | readarray -tn 0 DB_ERR_LINES <<<"$DB_ERR" 28 | echo "${DB_ERR_LINES[@]: -1}" 29 | echo "[ Use DB_WAIT_DEBUG=1 in netbox.env to print full traceback for errors here ]" 30 | fi 31 | echo "⏳ Waiting on DB... (${CUR_DB_WAIT_TIME}s / ${MAX_DB_WAIT_TIME}s)" 32 | sleep "${DB_WAIT_TIMEOUT}" 33 | CUR_DB_WAIT_TIME=$((CUR_DB_WAIT_TIME + DB_WAIT_TIMEOUT)) 34 | done 35 | if [ "${CUR_DB_WAIT_TIME}" -ge "${MAX_DB_WAIT_TIME}" ]; then 36 | echo "❌ Waited ${MAX_DB_WAIT_TIME}s or more for the DB to become ready." 37 | exit 1 38 | fi 39 | # Check if update is needed 40 | if ! ./manage.py migrate --check >/dev/null 2>&1; then 41 | echo "⚙️ Applying database migrations" 42 | ./manage.py migrate --no-input 43 | echo "⚙️ Running trace_paths" 44 | ./manage.py trace_paths --no-input 45 | echo "⚙️ Removing stale content types" 46 | ./manage.py remove_stale_contenttypes --no-input 47 | echo "⚙️ Removing expired user sessions" 48 | ./manage.py clearsessions 49 | echo "⚙️ Building search index (lazy)" 50 | ./manage.py reindex --lazy 51 | fi 52 | 53 | # Create Superuser if required 54 | if [ "$SKIP_SUPERUSER" == "true" ]; then 55 | echo "↩️ Skip creating the superuser" 56 | else 57 | if [ -z ${SUPERUSER_NAME+x} ]; then 58 | SUPERUSER_NAME='admin' 59 | fi 60 | if [ -z ${SUPERUSER_EMAIL+x} ]; then 61 | SUPERUSER_EMAIL='admin@example.com' 62 | fi 63 | if [ -f "/run/secrets/superuser_password" ]; then 64 | SUPERUSER_PASSWORD="$(" "${@}" 25 | } 26 | 27 | # check errors shall exit with code 1 28 | 29 | check_clean_repo() { 30 | changes=$(git status --porcelain 2>/dev/null) 31 | if [ ${?} ] && [ -n "$changes" ]; then 32 | echo_nok "There are git changes pending:" 33 | echo "$changes" 34 | echo_hint "Please clean the repository before continueing: git stash --include-untracked" 35 | exit 1 36 | fi 37 | echo_ok "Repository has no pending changes." 38 | } 39 | 40 | check_branch() { 41 | expected_branch="${1}" 42 | actual_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null) 43 | if [ ${?} ] && [ "${actual_branch}" != "${expected_branch}" ]; then 44 | echo_nok "Current branch should be '${expected_branch}', but is '${actual_branch}'." 45 | echo_hint "Please change to the '${expected_branch}' branch: git checkout ${expected_branch}" 46 | exit 1 47 | fi 48 | echo_ok "The current branch is '${actual_branch}'." 49 | } 50 | 51 | check_upstream() { 52 | expected_upstream_branch="origin/${1}" 53 | actual_upstream_branch=$(git rev-parse --abbrev-ref '@{upstream}' 2>/dev/null) 54 | if [ ${?} ] && [ "${actual_upstream_branch}" != "${expected_upstream_branch}" ]; then 55 | echo_nok "Current upstream branch should be '${expected_upstream_branch}', but is '${actual_upstream_branch}'." 56 | echo_hint "Please set '${expected_upstream_branch}' as the upstream branch: git branch --set-upstream-to=${expected_upstream_branch}" 57 | exit 1 58 | fi 59 | echo_ok "The current upstream branch is '${actual_upstream_branch}'." 60 | } 61 | 62 | check_origin() { 63 | expected_origin="git@github.com:${REPO}.git" 64 | actual_origin=$(git remote get-url origin 2>/dev/null) 65 | if [ ${?} ] && [ "${actual_origin}" != "${expected_origin}" ]; then 66 | echo_nok "The url of origin is '${actual_origin}', but '${expected_origin}' is expected." 67 | echo_hint "Please set '${expected_origin}' as the url for origin: git origin set-url '${expected_origin}'" 68 | exit 1 69 | fi 70 | echo_ok "The current origin url is '${actual_origin}'." 71 | } 72 | 73 | check_latest() { 74 | git fetch --tags origin 75 | 76 | local_head_commit=$(git rev-parse HEAD 2>/dev/null) 77 | remote_head_commit=$(git rev-parse FETCH_HEAD 2>/dev/null) 78 | if [ "${local_head_commit}" != "${remote_head_commit}" ]; then 79 | echo_nok "HEAD is at '${local_head_commit}', but FETCH_HEAD is at '${remote_head_commit}'." 80 | echo_hint "Please ensure that you have pushed and pulled all the latest chanegs: git pull --prune --rebase origin; git push origin" 81 | exit 1 82 | fi 83 | echo_ok "HEAD and FETCH_HEAD both point to '${local_head_commit}'." 84 | } 85 | 86 | check_tag() { 87 | local tag 88 | 89 | tag=$(/dev/null >/dev/null; then 91 | echo_nok "The tag '${tag}' already points to '$(git rev-parse "${tag}" 2>/dev/null)'." 92 | echo_hint "Please ensure that the 'VERSION' file has been updated before trying to release: echo X.Y.Z > VERSION" 93 | exit 1 94 | fi 95 | echo_ok "The tag '${tag}' does not exist yet." 96 | } 97 | 98 | check_develop() { 99 | echomoji 📋 "?" "Checking 'develop' branch" 100 | 101 | check_branch develop 102 | check_upstream develop 103 | check_clean_repo 104 | check_latest 105 | } 106 | 107 | check_release() { 108 | echomoji 📋 "?" "Checking 'release' branch" 109 | 110 | check_upstream release 111 | check_clean_repo 112 | check_latest 113 | } 114 | 115 | # git errors shall exit with code 2 116 | 117 | git_switch() { 118 | echomoji 🔀 "≈" "Switching to '${1}' branch…" 119 | if ! git checkout "${1}" >/dev/null; then 120 | echo_nok "It was not possible to switch to the branch '${1}'." 121 | exit 2 122 | fi 123 | echo_ok "The branch is now '${1}'." 124 | } 125 | 126 | git_tag() { 127 | echomoji 🏷 "X" "Tagging version '${1}'…" 128 | if ! git tag "${1}"; then 129 | echo_nok "The tag '${1}' was not created because of an error." 130 | exit 2 131 | fi 132 | echo_ok "The tag '$(