├── .github └── workflows │ ├── build.yml │ └── publish-containers.yml ├── Dockerfile ├── LICENSE ├── README.adoc ├── build-context ├── entrypoint.sh └── supervisor │ └── conf │ └── interactive │ └── supervisord.conf └── renovate.json5 /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build image 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: main 7 | pull_request: 8 | 9 | env: 10 | REGISTRY: ghcr.io 11 | IMAGE_NAME: ${{ github.repository }} 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | permissions: 17 | contents: read 18 | packages: write 19 | strategy: 20 | matrix: 21 | platform: 22 | - linux/arm64 23 | - linux/amd64 24 | 25 | steps: 26 | - name: Checkout repository 27 | uses: actions/checkout@v4.2.2 28 | 29 | - name: Extract container metadata 30 | id: meta 31 | uses: docker/metadata-action@v5.7.0 32 | with: 33 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 34 | tags: | 35 | type=schedule 36 | type=ref,event=branch 37 | type=ref,event=pr 38 | type=semver,pattern={{version}} 39 | type=sha 40 | env: 41 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 42 | 43 | - name: Set up QEMU 44 | uses: docker/setup-qemu-action@v3.6.0 45 | 46 | - name: Setup Docker buildx 47 | uses: docker/setup-buildx-action@v3.10.0 48 | 49 | # Build image with Buildx (don't push) 50 | - name: Build image 51 | id: build-and-push 52 | uses: docker/build-push-action@v6.16.0 53 | with: 54 | context: . 55 | platforms: ${{ matrix.platform }} 56 | push: false 57 | tags: ${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | annotations: ${{ steps.meta.outputs.annotations }} 60 | provenance: mode=max 61 | sbom: true 62 | outputs: | 63 | type=image,name=target 64 | -------------------------------------------------------------------------------- /.github/workflows/publish-containers.yml: -------------------------------------------------------------------------------- 1 | name: Publish container image to GHCR 2 | 3 | on: 4 | release: 5 | types: 6 | - created 7 | 8 | env: 9 | REGISTRY: ghcr.io 10 | IMAGE_NAME: ${{ github.repository }} 11 | 12 | jobs: 13 | build-and-push: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | id-token: write 19 | 20 | steps: 21 | - name: Checkout repository 22 | uses: actions/checkout@v4 23 | 24 | - name: Set up QEMU for multi-arch builds 25 | uses: docker/setup-qemu-action@v3 26 | 27 | - name: Set up Docker Buildx 28 | uses: docker/setup-buildx-action@v3 29 | 30 | - name: Extract container metadata 31 | id: meta 32 | uses: docker/metadata-action@v5 33 | with: 34 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 35 | tags: | 36 | type=semver,pattern={{version}} 37 | type=ref,event=branch 38 | type=ref,event=pr 39 | 40 | - name: Log in to GHCR 41 | uses: docker/login-action@v3 42 | with: 43 | registry: ${{ env.REGISTRY }} 44 | username: ${{ github.actor }} 45 | password: ${{ secrets.GITHUB_TOKEN }} 46 | 47 | - name: Build and Push Docker Image 48 | uses: docker/build-push-action@v6 49 | with: 50 | context: . 51 | push: true 52 | platforms: linux/amd64,linux/arm64 53 | labels: ${{ steps.meta.outputs.labels }} 54 | tags: ${{ steps.meta.outputs.tags }} 55 | annotations: ${{ steps.meta.outputs.annotations }} 56 | 57 | - name: Inspect pushed image 58 | run: | 59 | docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} 60 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM eclipse-temurin:24.0.1_9-jdk-alpine-3.21 2 | 3 | ARG BUILD_CONTEXT="build-context" 4 | ARG UID=worker 5 | ARG GID=worker 6 | # renovate: pypi: unoserver 7 | ARG VERSION_UNOSERVER=3.2 8 | 9 | LABEL org.opencontainers.image.title="unoserver-docker" 10 | LABEL org.opencontainers.image.description="Container image that contains unoserver and libreoffice including large set of fonts for file format conversions" 11 | LABEL org.opencontainers.image.licenses="MIT" 12 | LABEL org.opencontainers.image.documentation="https://github.com/unoconv/unoserver-docker/blob/main/README.adoc" 13 | LABEL org.opencontainers.image.source="https://github.com/unoconv/unoserver-docker" 14 | LABEL org.opencontainers.image.url="https://github.com/unoconv/unoserver-docker" 15 | 16 | WORKDIR / 17 | 18 | RUN addgroup -S ${GID} && adduser -S ${UID} -G ${GID} 19 | 20 | RUN apk add --no-cache \ 21 | bash curl \ 22 | py3-pip \ 23 | libreoffice \ 24 | supervisor 25 | 26 | # fonts - https://wiki.alpinelinux.org/wiki/Fonts 27 | RUN apk add --no-cache \ 28 | font-noto font-noto-cjk font-noto-extra \ 29 | terminus-font \ 30 | ttf-font-awesome \ 31 | ttf-dejavu \ 32 | ttf-freefont \ 33 | ttf-hack \ 34 | ttf-inconsolata \ 35 | ttf-liberation \ 36 | ttf-mononoki \ 37 | ttf-opensans \ 38 | fontconfig && \ 39 | fc-cache -f 40 | 41 | RUN rm -rf /var/cache/apk/* /tmp/* 42 | 43 | # https://github.com/unoconv/unoserver/ 44 | RUN pip install --break-system-packages -U unoserver==${VERSION_UNOSERVER} 45 | 46 | # setup supervisor 47 | COPY --chown=${UID}:${GID} ${BUILD_CONTEXT} / 48 | RUN chmod +x entrypoint.sh && \ 49 | # mkdir -p /var/log/supervisor && \ 50 | # chown ${UID}:${GID} /var/log/supervisor && \ 51 | # mkdir -p /var/run && \ 52 | chown -R ${UID}:0 /run && \ 53 | chmod -R g=u /run 54 | 55 | USER ${UID} 56 | WORKDIR /home/worker 57 | ENV HOME="/home/worker" 58 | 59 | VOLUME ["/data"] 60 | EXPOSE 2003 61 | ENTRYPOINT ["/entrypoint.sh"] 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 unoconv 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.adoc: -------------------------------------------------------------------------------- 1 | = Unoserver Container Image 2 | 3 | Container image for unoserver 4 | 5 | == The environment 6 | 7 | This Container image uses Alpine Linux as a base image and provides: 8 | 9 | * link:https://www.libreoffice.org/[LibreOffice] 10 | * link:https://github.com/unoconv/unoserver[unoserver] 11 | 12 | * Fonts (Alpine packages) 13 | - font-noto 14 | - font-noto-cjk 15 | - font-noto-extra 16 | - terminus-font 17 | - ttf-font-awesome 18 | - ttf-dejavu 19 | - ttf-freefont 20 | - ttf-hack 21 | - ttf-inconsolata 22 | - ttf-liberation 23 | - ttf-mononoki 24 | - ttf-opensans 25 | 26 | == How to use it 27 | 28 | NOTE: The `docker` can normally be replaced with `podman` as well. 29 | 30 | === In interactive mode 31 | 32 | Just run: 33 | 34 | [source,bash] 35 | ---- 36 | docker run -it -v :/data/ ghcr.io/unoconv/unoserver-docker 37 | ---- 38 | 39 | After you start the container, you can use link:https://github.com/unoconv/unoserver#unoconvert[unoconvert] command to convert documents using LibreOffice. 40 | 41 | or to convert directly using unoconvert: 42 | 43 | [source,bash] 44 | ---- 45 | docker run -it -v :/data/ ghcr.io/unoconv/unoserver-docker unoconvert /data/document.docx /data/document.pdf 46 | ---- 47 | 48 | Docker maps your directory with /data directory in the container. 49 | 50 | You might need to add the option `:z` or `:Z` like `:/data/:z` or `:/data/:Z` if you are using SELinux. See link:https://docs.docker.com/storage/bind-mounts/#configure-the-selinux-label[Docker docs] or link:https://docs.podman.io/en/latest/markdown/podman-run.1.html#volume-v-source-volume-host-dir-container-dir-options[Podman docs]. 51 | 52 | 53 | === In non-interactive (remote) mode 54 | 55 | [source,bash] 56 | ---- 57 | docker run -p 2003:2003 ghcr.io/unoconv/unoserver-docker 58 | ---- 59 | 60 | After you start the container, you can use link:https://github.com/unoconv/unoserver#unoconvert[unoconvert] command to convert documents using LibreOffice. 61 | 62 | NOTE: You must use `--host-location` otherwise unoconvert will default to local although running unoserver in a container should be handled as remote. 63 | 64 | [source,bash] 65 | ---- 66 | unoconvert --host-location remote example.docx example.pdf 67 | ---- 68 | == FAQ 69 | 70 | Q. Why do I get an error during when converting is writing/exporting to file? 71 | 72 | A. It's very likely that you haven't given the container write permission to the mounted data directory. See: link:https://github.com/unoconv/unoserver-docker/issues/44[Issue #44] 73 | 74 | == How to contribute / do it yourself? 75 | 76 | === Requirements 77 | 78 | You need the following tools: 79 | 80 | * A bash compliant command line 81 | * Docker installed and in your path 82 | 83 | === How to build 84 | 85 | [source,bash] 86 | ---- 87 | docker build . 88 | ---- 89 | -------------------------------------------------------------------------------- /build-context/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e -u 3 | 4 | # Function to wait for unoserver to start 5 | wait_for_unoserver() { 6 | echo "Waiting for unoserver to start on port 2003..." 7 | while ! netstat -tln | grep -q 2003; do 8 | sleep 1 9 | done 10 | echo "unoserver started." 11 | } 12 | 13 | export PS1='\u@\h:\w\$ ' 14 | 15 | echo "using: $(libreoffice --version)" 16 | 17 | # if tty then assume that container is interactive 18 | if [ ! -t 0 ]; then 19 | echo "Running unoserver-docker in non-interactive." 20 | echo "For interactive mode use '-it', e.g. 'docker run -v /tmp:/data -it unoserver/unoserver-docker'." 21 | 22 | unoserver --interface 0.0.0.0 23 | # # run supervisord in foreground 24 | # supervisord -c "$SUPERVISOR_NON_INTERACTIVE_CONF" 25 | else 26 | echo "Running unoserver-docker in interactive mode." 27 | echo "For non-interactive mode omit '-it', e.g. 'docker run -p 2003:2003 unoserver/unoserver-docker'." 28 | 29 | # default parameters for supervisord 30 | export SUPERVISOR_INTERACTIVE_CONF='/supervisor/conf/interactive/supervisord.conf' 31 | export UNIX_HTTP_SERVER_PASSWORD=${UNIX_HTTP_SERVER_PASSWORD:-$(cat /proc/sys/kernel/random/uuid)} 32 | 33 | # run supervisord as detached 34 | supervisord -c "$SUPERVISOR_INTERACTIVE_CONF" 35 | 36 | # wait until unoserver started and listens on port 2002. 37 | wait_for_unoserver 38 | 39 | # if commands have been passed to container run them and exit, else start bash 40 | if [[ $# -gt 0 ]]; then 41 | eval "$@" 42 | else 43 | /bin/bash 44 | fi 45 | fi 46 | -------------------------------------------------------------------------------- /build-context/supervisor/conf/interactive/supervisord.conf: -------------------------------------------------------------------------------- 1 | [supervisord] 2 | pidfile = /var/run/supervisord.pid 3 | 4 | # Note that at log level debug, the supervisord log file will record the stderr/stdout output 5 | # of its child processes and extended info info about process state changes, 6 | # which is useful for debugging a process which isn’t starting properly. 7 | # and also makes it available using `docker logs [container]` 8 | #logfile = /var/log/supervisor/supervisord.log 9 | logfile = /dev/stdout 10 | logfile_maxbytes = 0 11 | loglevel = info 12 | 13 | # needed for supervisor to work 14 | [unix_http_server] 15 | file = /var/run/supervisor.sock 16 | chmod = 0700 17 | username = admin 18 | password = %(ENV_UNIX_HTTP_SERVER_PASSWORD)s 19 | 20 | # needed for supervisor to work 21 | [rpcinterface:supervisor] 22 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 23 | 24 | # [inet_http_server] 25 | # port = 127.0.0.1:9001 26 | # username = admin 27 | # password = %(ENV_INET_HTTP_SERVER_PASSWORD) 28 | 29 | [supervisorctl] 30 | serverurl = unix:///var/run/supervisor.sock 31 | 32 | [program:unoserver] 33 | command = unoserver 34 | #stdout_logfile = /var/log/supervisor/%(program_name)s.log 35 | stdout_logfile = /dev/stdout 36 | stderr_logfile = /dev/stderr 37 | stdout_logfile_maxbytes = 0 38 | stderr_logfile_maxbytes = 0 -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "regexManagers": [ 3 | { 4 | "fileMatch": ["^Dockerfile$"], 5 | "matchStrings": [ 6 | "# renovate: pypi: unoserver.*\\sARG VERSION_UNOSERVER=(?.*)" 7 | ], 8 | "datasourceTemplate": "pypi", 9 | "depNameTemplate": "unoserver", 10 | "versioningTemplate": "pep440" 11 | } 12 | ], 13 | "packageRules": [ 14 | { 15 | "matchDatasources": ["pypi"], 16 | "matchPackageNames": ["unoserver"], 17 | "automerge": true, 18 | "automergeType": "pr", 19 | "automergeStrategy": "branch", 20 | "matchUpdateTypes": ["minor", "patch"] 21 | } 22 | ] 23 | } --------------------------------------------------------------------------------