├── .github ├── dependabot.yml ├── version_extractor.sh └── workflows │ ├── build_client.yml │ └── build_server.yml ├── LICENSE ├── README.md ├── client ├── Dockerfile ├── Dockerfile-alpine ├── Makefile ├── entrypoint.sh └── requirements.txt └── server ├── Dockerfile ├── Dockerfile-alpine ├── Makefile ├── entrypoint.sh └── requirements.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions. 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "daily" 8 | open-pull-requests-limit: 10 9 | # Maintain dependencies for Dockerfiles. 10 | - package-ecosystem: "docker" 11 | directory: "/client" 12 | schedule: 13 | interval: "daily" 14 | open-pull-requests-limit: 10 15 | - package-ecosystem: "docker" 16 | directory: "/server" 17 | schedule: 18 | interval: "daily" 19 | open-pull-requests-limit: 10 20 | # Maintain dependencies for pip packages. 21 | - package-ecosystem: "pip" 22 | directory: "/client" 23 | schedule: 24 | interval: "daily" 25 | open-pull-requests-limit: 10 26 | - package-ecosystem: "pip" 27 | directory: "/server" 28 | schedule: 29 | interval: "daily" 30 | open-pull-requests-limit: 10 31 | -------------------------------------------------------------------------------- /.github/version_extractor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -eo pipefail 3 | 4 | ################################################################################ 5 | # 6 | # This script will try to extract the version of the package of interest from 7 | # the file targeted. 8 | # 9 | # $1: The file to scan 10 | # $2: String defining if it is the server or client we are looking at 11 | # 12 | ################################################################################ 13 | 14 | 15 | version=$(sed -n -r -e 's/\s*devpi-'"${2}"'==([1-9]+\.[0-9]+\.[0-9]+).*$/\1/p' "${1}") 16 | 17 | if [ -z "${version}" ]; then 18 | echo "Could not extract '${2}' version from '${1}'" 19 | exit 1 20 | fi 21 | 22 | echo "::set-output name=APP_MAJOR::$(echo ${version} | cut -d. -f 1)" 23 | echo "::set-output name=APP_MINOR::$(echo ${version} | cut -d. -f 1-2)" 24 | echo "::set-output name=APP_PATCH::$(echo ${version} | cut -d. -f 1-3)" 25 | -------------------------------------------------------------------------------- /.github/workflows/build_client.yml: -------------------------------------------------------------------------------- 1 | name: "build-client" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "main" 8 | - "master" 9 | paths: 10 | - "client/**" 11 | pull_request: 12 | branches: 13 | - "main" 14 | - "master" 15 | paths: 16 | - "client/Dockerfile*" 17 | - "client/requirements.txt" 18 | 19 | jobs: 20 | docker_buildx_debian: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 15 23 | steps: 24 | - name: Perform setup steps 25 | uses: JonasAlfredsson/checkout-qemu-buildx@v2 26 | with: 27 | should_login: ${{ github.event_name != 'pull_request' }} 28 | username: ${{ secrets.DOCKERHUB_USERNAME }} 29 | password: ${{ secrets.DOCKERHUB_TOKEN }} 30 | 31 | - name: Extract version numbers from requirements.txt 32 | id: tagger 33 | run: bash .github/version_extractor.sh client/requirements.txt client 34 | 35 | - name: Build and push latest Debian image 36 | uses: docker/build-push-action@v6.18.0 37 | with: 38 | context: ./client 39 | file: ./client/Dockerfile 40 | platforms: | 41 | linux/amd64 42 | linux/386 43 | linux/arm64 44 | linux/arm/v7 45 | push: ${{ github.event_name != 'pull_request' }} 46 | tags: | 47 | jonasal/devpi-client:latest 48 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_MAJOR }} 49 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_MINOR }} 50 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_PATCH }} 51 | 52 | 53 | docker_buildx_alpine: 54 | runs-on: ubuntu-latest 55 | timeout-minutes: 15 56 | steps: 57 | - name: Perform setup steps 58 | uses: JonasAlfredsson/checkout-qemu-buildx@v2 59 | with: 60 | should_login: ${{ github.event_name != 'pull_request' }} 61 | username: ${{ secrets.DOCKERHUB_USERNAME }} 62 | password: ${{ secrets.DOCKERHUB_TOKEN }} 63 | 64 | - name: Extract version numbers from requirements.txt 65 | id: tagger 66 | run: bash .github/version_extractor.sh client/requirements.txt client 67 | 68 | - name: Build and push latest Alpine image 69 | uses: docker/build-push-action@v6.18.0 70 | with: 71 | context: ./client 72 | file: ./client/Dockerfile-alpine 73 | platforms: | 74 | linux/amd64 75 | linux/386 76 | linux/arm64 77 | linux/arm/v7 78 | push: ${{ github.event_name != 'pull_request' }} 79 | tags: | 80 | jonasal/devpi-client:latest-alpine 81 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_MAJOR }}-alpine 82 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_MINOR }}-alpine 83 | jonasal/devpi-client:${{ steps.tagger.outputs.APP_PATCH }}-alpine 84 | -------------------------------------------------------------------------------- /.github/workflows/build_server.yml: -------------------------------------------------------------------------------- 1 | name: "build-server" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - "main" 8 | - "master" 9 | paths: 10 | - "server/**" 11 | pull_request: 12 | branches: 13 | - "main" 14 | - "master" 15 | paths: 16 | - "server/Dockerfile*" 17 | - "server/requirements.txt" 18 | 19 | jobs: 20 | docker_buildx_debian: 21 | runs-on: ubuntu-latest 22 | timeout-minutes: 40 23 | steps: 24 | - name: Perform setup steps 25 | uses: JonasAlfredsson/checkout-qemu-buildx@v2 26 | with: 27 | should_login: ${{ github.event_name != 'pull_request' }} 28 | username: ${{ secrets.DOCKERHUB_USERNAME }} 29 | password: ${{ secrets.DOCKERHUB_TOKEN }} 30 | 31 | - name: Extract version numbers from requirements.txt 32 | id: tagger 33 | run: bash .github/version_extractor.sh server/requirements.txt server 34 | 35 | - name: Build and push latest Debian image 36 | uses: docker/build-push-action@v6.18.0 37 | with: 38 | context: ./server 39 | file: ./server/Dockerfile 40 | platforms: | 41 | linux/amd64 42 | linux/386 43 | linux/arm64 44 | linux/arm/v7 45 | push: ${{ github.event_name != 'pull_request' }} 46 | tags: | 47 | jonasal/devpi-server:latest 48 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_MAJOR }} 49 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_MINOR }} 50 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_PATCH }} 51 | 52 | 53 | docker_buildx_alpine: 54 | runs-on: ubuntu-latest 55 | timeout-minutes: 40 56 | steps: 57 | - name: Perform setup steps 58 | uses: JonasAlfredsson/checkout-qemu-buildx@v2 59 | with: 60 | should_login: ${{ github.event_name != 'pull_request' }} 61 | username: ${{ secrets.DOCKERHUB_USERNAME }} 62 | password: ${{ secrets.DOCKERHUB_TOKEN }} 63 | 64 | - name: Extract version numbers from requirements.txt 65 | id: tagger 66 | run: bash .github/version_extractor.sh server/requirements.txt server 67 | 68 | - name: Build and push latest Alpine image 69 | uses: docker/build-push-action@v6.18.0 70 | with: 71 | context: ./server 72 | file: ./server/Dockerfile-alpine 73 | platforms: | 74 | linux/amd64 75 | linux/386 76 | linux/arm64 77 | linux/arm/v7 78 | push: ${{ github.event_name != 'pull_request' }} 79 | tags: | 80 | jonasal/devpi-server:latest-alpine 81 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_MAJOR }}-alpine 82 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_MINOR }}-alpine 83 | jonasal/devpi-server:${{ steps.tagger.outputs.APP_PATCH }}-alpine 84 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jonas Alfredsson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docker-devpi 2 | 3 | Docker images for both [`devpi-server`][1] and [`devpi-client`][2], with both 4 | Debian and Alpine versions available. 5 | 6 | [Devpi][3] allows you to host a local PyPi cache, along with the ability for you to 7 | add your own packages that you do not want to upload publicly. 8 | 9 | > :information_source: Docker tags use the same version numberings as the devpi 10 | > packges on PyPi ([server][5] & [client][6]), but can be viewed on DockerHub 11 | > as well: [server][7] & [client][8]. 12 | 13 | 14 | # Usage 15 | 16 | This repository is split into two parts: the [server](#server) and the 17 | [client](#client). You will of course need a working server before there is 18 | any point in using the client. 19 | 20 | ## Server 21 | 22 | ### Environment Variables 23 | - `DEVPI_PASSWORD`: The password to set for the "root" user on init (**required**). 24 | 25 | ### Run it 26 | 27 | The `DEVPI_PASSWORD` environment variable will set the root password on the 28 | first startup of this image. Set it to something more secure than what I use 29 | as an example here. 30 | 31 | ```bash 32 | docker run -it -p 3141:3141 \ 33 | -e DEVPI_PASSWORD=password \ 34 | -v $(pwd)/data-server:/devpi/server \ 35 | --name devpi-server jonasal/devpi-server:latest 36 | ``` 37 | 38 | The command will also host mount the data directory from the server to your 39 | local folder, in order to persist data between restarts. 40 | 41 | > The first time this container is started a full re-index of the upstream PyPi 42 | > will commence, so the logs might be spammed with "Committing 2500 new 43 | > documents to search index." for a while. 44 | 45 | It is possible to customize the devpi instance with any of the available 46 | [arguments][1] by appending them to the command displayed above: 47 | 48 | ```bash 49 | docker run -it <...> jonasal/devpi-server:latest \ 50 | --threads 4 \ 51 | --debug 52 | ``` 53 | 54 | 55 | ## Client 56 | 57 | ### Environment Variables 58 | - `DEVPI_URL`: The URL to the devpi instance to connect to (default: `http://localhost:3141/root/pypi`) 59 | - `DEVPI_USER`: The username to login to the devpi instance with (default: `""`) 60 | - `DEVPI_PASSWORD`: The password for the user when logging in (default: `""`) 61 | 62 | ### Run it 63 | 64 | The `DEVPI_URL` environment variable is the only one that needs to be set, and 65 | it will use the default unless you specify something else. The other two are 66 | optional, and can be defined for convenience so you do not have to manually 67 | login every time you start this container. Furthermore, if `DEVPI_USER` is set 68 | but `DEVPI_PASSWORD` is empty you will be prompted for the password. 69 | 70 | The following command expects that there is a functional devpi instance running 71 | on the local computer, and that the "root" user has a password set. The host 72 | mounted directory may be used to transfer files in and out of the container. 73 | 74 | ```bash 75 | docker run -it --rm --network host \ 76 | -e DEVPI_USER=root \ 77 | -e DEVPI_PASSWORD=password \ 78 | -v $(pwd)/data-client:/devpi/client \ 79 | jonasal/devpi-client:latest 80 | ``` 81 | 82 | Important note here is that this container uses the "host" network, else the 83 | `localhost` in the URL would just make so that the container tried to contact 84 | itself and would thus not reach the container running the server program. 85 | 86 | 87 | ## Configure pip 88 | 89 | For a quick test to see if the `devpi-server` actually works you can make a 90 | one-off installation like this: 91 | 92 | ```bash 93 | pip install -i http://localhost:3141/root/pypi simplejson 94 | ``` 95 | 96 | The server logs should move and the `pip` installation should be successful. 97 | 98 | To then make this setting a bit more permanent you can edit `~/.pip/pip.conf` 99 | with this: 100 | 101 | ```ini 102 | [global] 103 | index-url = http://localhost:3141/root/pypi 104 | ``` 105 | 106 | Following installations should then be going through your local cache. 107 | 108 | 109 | ## Set Up Local Index 110 | 111 | By default devpi creates an index called `root/pypi`, which serves as a proxy 112 | and cache for [PyPI][4], and you can’t upload your own packages to it. 113 | 114 | In order to upload local packages we need to create another index, and to make 115 | our lives easier we will also configure it so that it "inherits" from the 116 | `root/pypi` index. What this meas is that if our search for a package in the 117 | local index fails it will fall back to the `root/pypi` index and try to find it 118 | there. Thus we can add all our private packages without losing the ability to 119 | find all the public ones. 120 | 121 | To achieve this we will start the `devpi-client` container as root (we need 122 | to be able to make modifications): 123 | 124 | ```bash 125 | docker run -it --rm --network host \ 126 | -e DEVPI_USER=root \ 127 | -e DEVPI_PASSWORD=password \ 128 | -v $(pwd)/data-client:/devpi/client \ 129 | jonasal/devpi-client:latest 130 | ``` 131 | 132 | Inside the container we will first create another user: 133 | 134 | ```bash 135 | devpi user -c local password=something_long_and_complicated 136 | ``` 137 | 138 | Then we will create an index under this new user that inherits from the 139 | `root/pypi` index: 140 | 141 | ```bash 142 | devpi index -c local/stable bases=root/pypi volatile=False 143 | ``` 144 | 145 | Restart the container again, but this time specify the new index URL and the 146 | new user: 147 | 148 | ```bash 149 | docker run -it --rm --network host \ 150 | -e DEVPI_URL="http://localhost:3141/local/stable" \ 151 | -e DEVPI_USER=local \ 152 | -e DEVPI_PASSWORD=something_long_and_complicated \ 153 | -v $(pwd)/data-client:/devpi/client \ 154 | jonasal/devpi-client:latest 155 | ``` 156 | 157 | You can now upload files from the `/devpi/client` folder to the current index 158 | by running something similar to this: 159 | 160 | ```bash 161 | devpi upload /devpi/client private_package-0.1.0-py3-none-any.whl 162 | ``` 163 | 164 | After this you should be able to install it by pointing to the new index: 165 | 166 | ```bash 167 | pip install -i http://localhost:3141/local/stable private_package 168 | ``` 169 | 170 | 171 | ## Upgrading 172 | 173 | > :warning: This is semi-experimental since I am having trouble finding 174 | > official documentation on the proper way to do this. 175 | 176 | The best guide on upgrading devpi server I could find was [this one][9], so 177 | I tried to integrate that into the entrypoint. It is not very automated, but 178 | you will at least have full control of what it is doing the entire time. 179 | 180 | > :information_source: You should also only have to do this procedure when 181 | > there is a change in the database schema. Such a change [will warrant][10] a 182 | > **major** version bump, so this process is not necessary if you are just 183 | > upgrading a **minor** or a **patch** version. 184 | 185 | Begin by stopping any of the currently running devpi containers, and then run 186 | the same image again with the `/export` folder mounted to initiate an export 187 | process: 188 | 189 | ```bash 190 | docker run -it --rm -e DEVPI_PASSWORD=password \ 191 | -v $(pwd)/data-server:/devpi/server \ 192 | -v $(pwd)/tmp:/export \ 193 | jonasal/devpi-server:old-tag 194 | ``` 195 | 196 | If this is successful you should now rename the old data folder (don't delete 197 | it before you know the new one works): 198 | 199 | ```bash 200 | sudo mv data-server data-server.bak 201 | ``` 202 | 203 | Run the new image with the `/import` folder mounted in order to initiate an 204 | import process: 205 | 206 | ```bash 207 | docker run -it --rm -e DEVPI_PASSWORD=password \ 208 | -v $(pwd)/data-server:/devpi/server \ 209 | -v $(pwd)/tmp:/import \ 210 | jonasal/devpi-server:new-tag 211 | ``` 212 | 213 | When this one completes you can go back to running the image normally without 214 | any of the `import/export` folders mounted. This should most likely give you a 215 | functional upgraded instance of devpi. Cleanup of the extra folders can be done 216 | with this simple command 217 | 218 | ```bash 219 | sudo rm -r data-server.bak && sudo rm -r tmp 220 | ``` 221 | 222 | 223 | # Further Reading 224 | 225 | I got most of the information I needed to complete this project from the 226 | following sources, perhaps they are useful for you too: 227 | 228 | - [Devpi Quickstart Guide](https://devpi.net/docs/devpi/devpi/stable/+d/quickstart-server.html) 229 | - [Stefan Scherfke: Getting started with devpi](https://stefan.sofa-rockers.org/2017/11/09/getting-started-with-devpi/) 230 | - [@kyhau: devpiNotes.md](https://gist.github.com/kyhau/0b54386fe220877310b9) 231 | - [Mpho Mphego: How I Setup A Private Local PyPI Server Using Docker And Ansible](https://blog.mphomphego.co.za/blog/2021/06/15/How-I-setup-a-private-PyPI-server-using-Docker-and-Ansible.html) 232 | - [@kyhau: devpiServerUpgrade.md][9] 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | [1]: https://devpi.net/docs/devpi/devpi/stable/+d/userman/devpi_commands.html#devpi-command-reference-server 242 | [2]: https://devpi.net/docs/devpi/devpi/stable/+d/userman/devpi_commands.html 243 | [3]: https://doc.devpi.net 244 | [4]: https://pypi.org/ 245 | [5]: https://pypi.org/project/devpi-server/#history 246 | [6]: https://pypi.org/project/devpi-client/#history 247 | [7]: https://hub.docker.com/repository/docker/jonasal/devpi-server/tags?page=1&ordering=last_updated 248 | [8]: https://hub.docker.com/repository/docker/jonasal/devpi-client/tags?page=1&ordering=last_updated 249 | [9]: https://gist.github.com/kyhau/7707c6dfa25c2e14e345 250 | [10]: https://github.com/devpi/devpi/issues/439#issuecomment-1064329177 251 | -------------------------------------------------------------------------------- /client/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.4-slim 2 | 3 | ENV DEVPICLIENT_CLIENTDIR=/devpi/client 4 | RUN mkdir -p $DEVPICLIENT_CLIENTDIR 5 | WORKDIR $DEVPICLIENT_CLIENTDIR 6 | 7 | COPY requirements.txt /requirements.txt 8 | RUN pip install --no-cache-dir -r /requirements.txt 9 | 10 | COPY entrypoint.sh / 11 | ENTRYPOINT [ "/entrypoint.sh" ] 12 | 13 | CMD [ "bash" ] 14 | -------------------------------------------------------------------------------- /client/Dockerfile-alpine: -------------------------------------------------------------------------------- 1 | FROM python:3.13.4-alpine 2 | 3 | ENV DEVPICLIENT_CLIENTDIR=/devpi/client 4 | RUN mkdir -p $DEVPICLIENT_CLIENTDIR 5 | WORKDIR $DEVPICLIENT_CLIENTDIR 6 | 7 | COPY requirements.txt /requirements.txt 8 | RUN pip install --no-cache-dir -r /requirements.txt 9 | 10 | COPY entrypoint.sh / 11 | ENTRYPOINT [ "/entrypoint.sh" ] 12 | 13 | CMD [ "sh" ] 14 | -------------------------------------------------------------------------------- /client/Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: Makefile Dockerfile 4 | docker build -t jonasal/devpi-client:local . 5 | @echo "Done! Use docker run jonasal/devpi-client:local to run" 6 | 7 | build-alpine: Makefile Dockerfile 8 | docker build -t jonasal/devpi-client:local-alpine -f ./Dockerfile-alpine . 9 | @echo "Done! Use docker run jonasal/devpi-client:local-alpine to run" 10 | 11 | # Ssee link for more info about how these work: 12 | # https://github.com/JonasAlfredsson/docker-nginx-certbot/issues/28 13 | dev: Makefile Dockerfile 14 | docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 --tag jonasal/devpi-client:dev -f ./Dockerfile ./ 15 | 16 | dev-alpine: Makefile Dockerfile 17 | docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 --tag jonasal/devpi-client:dev-alpine -f ./Dockerfile-alpine ./ 18 | -------------------------------------------------------------------------------- /client/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | set -e 3 | 4 | # Connect to the defined devpi server URL, or default to localhost. 5 | devpi use "${DEVPI_URL:=http://localhost:3141/root/pypi}" 6 | 7 | # If a user is defined we try to login with that. 8 | if [ -n "${DEVPI_USER}" ]; then 9 | if [ -n "${DEVPI_PASSWORD}" ]; then 10 | devpi login --password "${DEVPI_PASSWORD}" "${DEVPI_USER}" 11 | else 12 | devpi login "${DEVPI_USER}" 13 | fi 14 | fi 15 | 16 | # Either just drop us into the shell, or execute whatever was given. 17 | exec "$@" 18 | -------------------------------------------------------------------------------- /client/requirements.txt: -------------------------------------------------------------------------------- 1 | devpi-client==7.2.0 2 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.13.4-slim 2 | 3 | ENV DEVPISERVER_SERVERDIR=/devpi/server 4 | RUN mkdir -p $DEVPISERVER_SERVERDIR && \ 5 | mkdir /entrypoint.d 6 | 7 | COPY requirements.txt /requirements.txt 8 | 9 | # Do a single run command to make the intermediary containers smaller. 10 | RUN set -ex && \ 11 | # Install packages necessary during the build phase (for all architectures). 12 | apt-get update && \ 13 | apt-get install -y --no-install-recommends \ 14 | build-essential \ 15 | libffi8 \ 16 | libffi-dev \ 17 | && \ 18 | # Install Python dependencies. 19 | pip install --no-cache-dir -r /requirements.txt && \ 20 | # Remove everything that is no longer necessary. 21 | apt-get remove --purge -y \ 22 | build-essential \ 23 | libffi-dev \ 24 | && \ 25 | apt-get autoremove -y && \ 26 | apt-get clean && \ 27 | rm -rf /var/lib/apt/lists/* && \ 28 | rm -rf /root/.cache 29 | 30 | COPY entrypoint.sh / 31 | ENTRYPOINT [ "/entrypoint.sh" ] 32 | CMD [ "" ] 33 | 34 | EXPOSE 3141 35 | -------------------------------------------------------------------------------- /server/Dockerfile-alpine: -------------------------------------------------------------------------------- 1 | FROM python:3.13.4-alpine 2 | 3 | ENV DEVPISERVER_SERVERDIR=/devpi/server 4 | RUN mkdir -p $DEVPISERVER_SERVERDIR && \ 5 | mkdir /entrypoint.d 6 | 7 | COPY requirements.txt /requirements.txt 8 | RUN apk add --no-cache \ 9 | gcc \ 10 | musl-dev \ 11 | libffi \ 12 | libffi-dev \ 13 | && \ 14 | pip install --no-cache-dir -r /requirements.txt \ 15 | && \ 16 | apk del \ 17 | gcc \ 18 | musl-dev \ 19 | libffi-dev 20 | 21 | COPY entrypoint.sh / 22 | ENTRYPOINT [ "/entrypoint.sh" ] 23 | CMD [ "" ] 24 | 25 | EXPOSE 3141 26 | -------------------------------------------------------------------------------- /server/Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | build: Makefile Dockerfile 4 | docker build -t jonasal/devpi-server:local . 5 | @echo "Done! Use docker run jonasal/devpi-server:local to run" 6 | 7 | build-alpine: Makefile Dockerfile 8 | docker build -t jonasal/devpi-server:local-alpine -f ./Dockerfile-alpine . 9 | @echo "Done! Use docker run jonasal/devpi-server:local-alpine to run" 10 | 11 | # See link for more info about how these work: 12 | # https://github.com/JonasAlfredsson/docker-nginx-certbot/issues/28 13 | dev: Makefile Dockerfile 14 | docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 --tag jonasal/devpi-server:dev -f ./Dockerfile ./ 15 | 16 | dev-alpine: Makefile Dockerfile 17 | docker buildx build --platform linux/amd64,linux/386,linux/arm64,linux/arm/v7 --tag jonasal/devpi-server:dev-alpine -f ./Dockerfile-alpine ./ 18 | -------------------------------------------------------------------------------- /server/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | # Helper function used to make all logging messages look similar. 5 | log() { 6 | echo "$(date '+%Y-%m-%d %H:%M:%S,000') $1 [entrypoint] $2" 7 | } 8 | 9 | # Check that we have some kind of password for the root user. 10 | if [ -z "${DEVPI_PASSWORD}" ]; then 11 | log "ERROR" "Root password cannot be empty" 12 | exit 1 13 | fi 14 | 15 | # Perform an export and exit in case the /export folder exists. 16 | if [ -d "/export" ]; then 17 | log "INFO" "Exporting current database" 18 | devpi-export $@ /export 19 | log "INFO" "Export finished" 20 | exit 0 21 | fi 22 | 23 | # Perform an import and exit in case the /import folder exists. 24 | if [ -d "/import" ]; then 25 | log "INFO" "Beginning import of data" 26 | devpi-import --root-passwd "${DEVPI_PASSWORD}" $@ /import 27 | log "INFO" "Import complete" 28 | exit 0 29 | fi 30 | 31 | # Execute any potential shell scripts in the entrypoint.d/ folder, or source 32 | # any file ending with ".envsh". 33 | find "/entrypoint.d/" -follow -type f -print | sort -V | while read -r f; do 34 | case "${f}" in 35 | *.envsh) 36 | if [ -x "${f}" ]; then 37 | log "INFO" "Sourcing ${f}"; 38 | . "${f}" 39 | else 40 | log "INFO" "Ignoring ${f}, not executable"; 41 | fi 42 | ;; 43 | *.sh) 44 | if [ -x "${f}" ]; then 45 | log "INFO" "Launching ${f}"; 46 | "${f}" 47 | else 48 | log "INFO" "Ignoring ${f}, not executable"; 49 | fi 50 | ;; 51 | *) 52 | log "INFO" "Ignoring ${f}" 53 | ;; 54 | esac 55 | done 56 | 57 | # Initialize devpi if there is no indication of it being run before. 58 | if [ ! -f "${DEVPISERVER_SERVERDIR}/.serverversion" ]; then 59 | devpi-init --root-passwd "${DEVPI_PASSWORD}" 60 | fi 61 | 62 | # Launch the server as the main process. 63 | # Latest version will block correctly and listens to Ctrl+C. 64 | devpi-server \ 65 | --host 0.0.0.0 \ 66 | --port 3141 \ 67 | --restrict-modify=root \ 68 | $@ 69 | -------------------------------------------------------------------------------- /server/requirements.txt: -------------------------------------------------------------------------------- 1 | devpi-server==6.15.0 2 | devpi-web==4.3.0 3 | --------------------------------------------------------------------------------