├── .github ├── .github_liveness.txt ├── FUNDING.yml └── workflows │ ├── hub.yml │ ├── images.yml │ └── keepalive.yml ├── Dockerfile ├── LICENSE ├── README.md ├── docker-entrypoint.sh ├── empty.sh ├── ls.sh └── trap.sh /.github/.github_liveness.txt: -------------------------------------------------------------------------------- 1 | # Last GitHub activity at: 2025-05-25T03:05:34+00:00 2 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: 4 | - efrecon 5 | #patreon: # Replace with a single Patreon username 6 | #open_collective: # Replace with a single Open Collective username 7 | #ko_fi: # Replace with a single Ko-fi username 8 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 9 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 10 | #liberapay: # Replace with a single Liberapay username 11 | #issuehunt: # Replace with a single IssueHunt username 12 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | #polar: # Replace with a single Polar username 14 | #buy_me_a_coffee: # Replace with a single Buy Me a Coffee username 15 | #custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 16 | -------------------------------------------------------------------------------- /.github/workflows/hub.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub Description 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | paths: 10 | - 'README.md' 11 | 12 | jobs: 13 | hub: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - 17 | name: Checkout 18 | uses: actions/checkout@v3 19 | with: 20 | submodules: true 21 | - 22 | # Note: This uses the password, not the token as this action would 23 | # otherwise not work. 24 | name: Update repo description at Docker Hub 25 | uses: peter-evans/dockerhub-description@v4 26 | with: 27 | username: ${{ secrets.DOCKERHUB_USERNAME }} 28 | password: ${{ secrets.DOCKERHUB_TOKEN }} 29 | repository: efrecon/webdav-client 30 | short-description: WebDAV client for Docker with easy access to all davfs2 options -------------------------------------------------------------------------------- /.github/workflows/images.yml: -------------------------------------------------------------------------------- 1 | name: Docker Images 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | - master 9 | 10 | jobs: 11 | ghcr: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - 15 | name: Set up QEMU 16 | uses: docker/setup-qemu-action@v3 17 | - 18 | name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v3 20 | - 21 | name: Login to GHCR 22 | uses: docker/login-action@v3 23 | with: 24 | registry: ghcr.io 25 | username: ${{ github.repository_owner }} 26 | password: ${{ secrets.GITHUB_TOKEN }} 27 | - 28 | name: Build and push GHCR images 29 | uses: docker/build-push-action@v5 30 | with: 31 | push: true 32 | platforms: linux/amd64,linux/arm64 33 | tags: ghcr.io/efrecon/webdav-client 34 | hub: 35 | runs-on: ubuntu-latest 36 | steps: 37 | - 38 | name: Set up QEMU 39 | uses: docker/setup-qemu-action@v3 40 | - 41 | name: Set up Docker Buildx 42 | uses: docker/setup-buildx-action@v3 43 | - 44 | name: Login to Docker Hub 45 | uses: docker/login-action@v3 46 | with: 47 | registry: docker.io 48 | username: ${{ secrets.DOCKERHUB_USERNAME }} 49 | password: ${{ secrets.DOCKERHUB_TOKEN }} 50 | - 51 | name: Build and push Docker Hub images 52 | uses: docker/build-push-action@v5 53 | with: 54 | push: true 55 | platforms: linux/amd64,linux/arm64 56 | tags: efrecon/webdav-client 57 | -------------------------------------------------------------------------------- /.github/workflows/keepalive.yml: -------------------------------------------------------------------------------- 1 | name: keepalive 2 | 3 | on: 4 | schedule: 5 | # Run every sunday at 1:27 UTC 6 | - cron: '27 1 * * SUN' 7 | 8 | jobs: 9 | keepalive: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: keepalive 13 | uses: efrecon/gh-action-keepalive@main 14 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.19.1 2 | 3 | # Metadata 4 | LABEL MAINTAINER=efrecon+github@gmail.com 5 | LABEL org.opencontainers.image.title="efrecon/webdav-client" 6 | LABEL org.opencontainers.image.description="Mount WebDAV shares from within a container and expose them to host/containers" 7 | LABEL org.opencontainers.image.authors="Emmanuel Frécon " 8 | LABEL org.opencontainers.image.url="https://github.com/efrecon/docker-webdav-client" 9 | LABEL org.opencontainers.image.documentation="https://github.com/efrecon/docker-webdav-client/README.md" 10 | LABEL org.opencontainers.image.source="https://github.com/efrecon/docker-webdav-client/Dockerfile" 11 | 12 | # Specify URL, username and password to communicate with the remote webdav 13 | # resource. When using _FILE, the password will be read from that file itself, 14 | # which helps passing further passwords using Docker secrets. 15 | ENV WEBDRIVE_URL= 16 | ENV WEBDRIVE_USERNAME= 17 | ENV WEBDRIVE_PASSWORD= 18 | ENV WEBDRIVE_PASSWORD_FILE= 19 | 20 | # User ID of share owner 21 | ENV OWNER=0 22 | 23 | # Location of directory where to mount the drive into the container. 24 | ENV WEBDRIVE_MOUNT=/mnt/webdrive 25 | 26 | # In addition, all variables that start with DAVFS2_ will be converted into 27 | # davfs2 compatible options for that share, once the leading DAVFS2_ have been 28 | # removed and once converted to lower case. So, for example, specifying 29 | # DAVFS2_ASK_AUTH=0 will set the davfs2 configuration option ask_auth to 0 for 30 | # that share. See the manual for the list of available options. 31 | 32 | RUN apk --no-cache add ca-certificates davfs2 tini 33 | 34 | COPY *.sh /usr/local/bin/ 35 | 36 | # Following should match the WEBDRIVE_MOUNT environment variable. 37 | VOLUME [ "/mnt/webdrive" ] 38 | 39 | # The default is to perform all system-level mounting as part of the entrypoint 40 | # to then have a command that will keep listing the files under the main share. 41 | # Listing the files will keep the share active and avoid that the remote server 42 | # closes the connection. 43 | ENTRYPOINT [ "tini", "-g", "--", "/usr/local/bin/docker-entrypoint.sh" ] 44 | CMD [ "ls.sh" ] 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Emmanuel Frecon 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dockerised WebDAV Client 2 | 3 | This Docker [image] (also at the [GHCR]) and associated [project] facilitate 4 | mounting of remote WebDAV resources into containers. Mounting is implemented 5 | using [davfs2] and the image makes it possible to set all supported davfs 6 | [configuration] options for the share. The image basically implements a docker 7 | [volume] on the cheap: Used with the proper creation options (see below) , you 8 | should be able to bind-mount back the remote bucket onto a host directory. This 9 | directory will make the content of the bucket available to processes, but also 10 | all other containers on the host. The image automatically unmounts the remote 11 | bucket on container termination. 12 | 13 | [image]: https://hub.docker.com/r/efrecon/webdav-client 14 | [GHCR]: https://github.com/efrecon/docker-webdav-client/pkgs/container/webdav-client 15 | [project]: https://github.com/efrecon/docker-webdav-client 16 | [davfs2]: http://savannah.nongnu.org/projects/davfs2 17 | [configuration]: https://man.cx/davfs2.conf(5) 18 | [volume]: https://docs.docker.com/storage/ 19 | 20 | ## Example 21 | 22 | Provided the existence of a directory called `/mnt/tmp` on the host, the 23 | following command would mount a remote WebDAV resource, ensure that 24 | authentication details are never requested from the command-line and bind-mount 25 | the remote resource onto the host's `/mnt/tmp` in a way that makes the remote 26 | files accessible to processes and/or other containers running on the same host. 27 | 28 | ```Shell 29 | docker run -it --rm \ 30 | --device /dev/fuse \ 31 | --cap-add SYS_ADMIN \ 32 | --security-opt "apparmor=unconfined" \ 33 | --env "WEBDRIVE_USERNAME=" \ 34 | --env "WEBDRIVE_PASSWORD=" \ 35 | --env "WEBDRIVE_URL=https://dav.box.com/dav" \ 36 | --env "DAVFS2_ASK_AUTH=0" \ 37 | -v /mnt/tmp:/mnt/webdrive:rshared \ 38 | efrecon/webdav-client 39 | ``` 40 | 41 | The `--device`, `--cap-add` and `--security-opt` options and their values are to 42 | make sure that the container will be able to make available the WebDAV resource 43 | using FUSE. `rshared` is what ensures that bind mounting makes the files and 44 | directories available back to the host and recursively to other containers. 45 | 46 | ## Container Options 47 | 48 | A series of environment variables, most led by `WEBDRIVE_` can be used to 49 | parametrise the container: 50 | 51 | * `WEBDRIVE_URL` is the URL at which to find the WebDAV resource. 52 | * `WEBDRIVE_USERNAME` is the user to use for accessing the resource. 53 | * `WEBDRIVE_PASSWORD` is the password for that user. 54 | * `WEBDRIVE_PASSWORD_FILE` points instead to a file that will contain the 55 | password for the user. When this is present, the password will be taken from 56 | the file instead of from the `WEBDRIVE_PASSWORD` variable. If that variable 57 | existed, it will be disregarded. This makes it easy to pass passwords using 58 | Docker [secrets]. 59 | * `WEBDRIVE_MOUNT` is the location within the container where to mount the 60 | WebDAV resource. This defaults to `/mnt/webdrive` and is not really meant to 61 | be changed. 62 | * `OWNER` is the user ID for the owner of the share inside the container. 63 | 64 | [secrets]: https://docs.docker.com/engine/swarm/secrets/ 65 | 66 | ## davFS Options 67 | 68 | All [configuration] options recognised by davFS can be given for that particular 69 | share. Environment variables should be created out of the name of the 70 | configuration option for this to work. Any existing option should be translated 71 | to uppercase and led by the keyword `DAVFS2_` to be recognised. So to set the 72 | davfs2 option called `ask_auth` to `0`, you would set the environment variable 73 | `DAVFS2_ASK_AUTH` to `0`. 74 | 75 | ## Commands 76 | 77 | By default, containers based on this image will keep listing the content of the 78 | mounted directory at regular intervals. This is implemented by the 79 | [command](./ls.sh) that it is designed to execute once the remote WebDAV 80 | resource has been mounted. If you did not wish this behaviour, pass `empty.sh` 81 | as the command instead. 82 | 83 | Note that both of these commands ensure that the remote WebDAV resource is 84 | unmounted from the mountpoint at termination, so you should really pick one or 85 | the other to allow for proper operation. If the mountpoint was not unmounted, 86 | your mount system will be unstable as it will contain an unknown entry. 87 | 88 | Automatic unmounting is achieved through a combination of a `trap` in the 89 | command being executed and [tini]. [tini] is made available directly in this 90 | image to make it possible to run in [Swarm] environments. 91 | 92 | [tini]: https://github.com/krallin/tini 93 | [Swarm]: https://docs.docker.com/engine/swarm/ 94 | -------------------------------------------------------------------------------- /docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | # Where are we going to mount the remote webdav resource in our container. 4 | DEST=${WEBDRIVE_MOUNT:-/mnt/webdrive} 5 | 6 | # Check variables and defaults 7 | if [ -z "${WEBDRIVE_URL}" ]; then 8 | echo "No URL specified!" 9 | exit 10 | fi 11 | if [ -z "${WEBDRIVE_USERNAME}" ]; then 12 | echo "No username specified, is this on purpose?" 13 | # set up user name for anonymous access: 14 | WEBDRIVE_USERNAME="-" 15 | fi 16 | if [ -n "${WEBDRIVE_PASSWORD_FILE}" ]; then 17 | WEBDRIVE_PASSWORD=$(read ${WEBDRIVE_PASSWORD_FILE}) 18 | fi 19 | if [ -z "${WEBDRIVE_PASSWORD}" ]; then 20 | echo "No password specified, is this on purpose?" 21 | # set up password for anonymous access: 22 | WEBDRIVE_PASSWORD="-" 23 | fi 24 | 25 | # Create secrets file and forget about the password once done (this will have 26 | # proper effects when the PASSWORD_FILE-version of the setting is used) 27 | echo "$DEST $WEBDRIVE_USERNAME $WEBDRIVE_PASSWORD" >> /etc/davfs2/secrets 28 | unset WEBDRIVE_PASSWORD 29 | 30 | # Add davfs2 options out of all the environment variables starting with DAVFS2_ 31 | # at the end of the configuration file. Nothing is done to check that these are 32 | # valid davfs2 options, use at your own risk. 33 | if [ -n "$(env | grep "DAVFS2_")" ]; then 34 | echo "" >> /etc/davfs2/davfs2.conf 35 | echo "[$DEST]" >> /etc/davfs2/davfs2.conf 36 | for VAR in $(env); do 37 | if [ -n "$(echo "$VAR" | grep -E '^DAVFS2_')" ]; then 38 | OPT_NAME=$(echo "$VAR" | sed -r "s/DAVFS2_([^=]*)=.*/\1/g" | tr '[:upper:]' '[:lower:]') 39 | VAR_FULL_NAME=$(echo "$VAR" | sed -r "s/([^=]*)=.*/\1/g") 40 | VAL=$(eval echo \$$VAR_FULL_NAME) 41 | echo "$OPT_NAME $VAL" >> /etc/davfs2/davfs2.conf 42 | fi 43 | done 44 | fi 45 | 46 | # Create destination directory if it does not exist. 47 | if [ ! -d $DEST ]; then 48 | mkdir -p $DEST 49 | fi 50 | 51 | # Deal with ownership 52 | if [ $OWNER -gt 0 ]; then 53 | adduser webdrive -u $OWNER -D -G users 54 | chown webdrive $DEST 55 | fi 56 | 57 | # Mount and verify that something is present. davfs2 always creates a lost+found 58 | # sub-directory, so we can use the presence of some file/dir as a marker to 59 | # detect that mounting was a success. Execute the command on success. 60 | mount -t davfs $WEBDRIVE_URL $DEST -o uid=$OWNER,gid=users,dir_mode=755,file_mode=755 61 | if [ -n "$(ls -1A $DEST)" ]; then 62 | echo "Mounted $WEBDRIVE_URL onto $DEST" 63 | exec "$@" 64 | else 65 | echo "Nothing found in $DEST, giving up!" 66 | fi 67 | -------------------------------------------------------------------------------- /empty.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | DEST=${WEBDRIVE_MOUNT:-/mnt/webdrive} 4 | . trap.sh 5 | 6 | tail -f /dev/null -------------------------------------------------------------------------------- /ls.sh: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env sh 2 | 3 | PERIOD=${1:-60} 4 | DEST=${WEBDRIVE_MOUNT:-/mnt/webdrive} 5 | 6 | . trap.sh 7 | 8 | while true; do 9 | ls $DEST 10 | sleep $PERIOD 11 | done -------------------------------------------------------------------------------- /trap.sh: -------------------------------------------------------------------------------- 1 | exit_script() { 2 | SIGNAL=$1 3 | echo "Caught $SIGNAL! Unmounting ${DEST}..." 4 | umount -l ${DEST} 5 | dav2fs=$(ps -o pid= -o comm= | grep mount.davfs | sed -E 's/\s*(\d+)\s+.*/\1/g') 6 | if [ -n "$dav2fs" ]; then 7 | echo "Forwarding $SIGNAL to $dav2fs" 8 | while $(kill -$SIGNAL $dav2fs 2> /dev/null); do 9 | sleep 1 10 | done 11 | fi 12 | trap - $SIGNAL # clear the trap 13 | exit $? 14 | } 15 | 16 | trap "exit_script INT" INT 17 | trap "exit_script TERM" TERM 18 | --------------------------------------------------------------------------------