├── .github └── workflows │ └── push-to-docker-hub.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── requirements.txt └── update.sh /.github/workflows/push-to-docker-hub.yaml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | name: Publish Docker image 7 | 8 | on: 9 | push: 10 | branches: 11 | - 'master' 12 | - 'devel' 13 | - 'v*' 14 | pull_request: 15 | branches: 16 | - 'master' 17 | 18 | jobs: 19 | push_to_registry: 20 | name: Push Docker image to Docker Hub 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Check out the repo 24 | uses: actions/checkout@v4 25 | 26 | - name: Set up QEMU 27 | uses: docker/setup-qemu-action@v3 28 | 29 | - name: Set up Docker Buildx 30 | id: buildx 31 | uses: docker/setup-buildx-action@v3 32 | 33 | - name: Log in to Docker Hub 34 | if: github.event_name != 'pull_request' 35 | uses: docker/login-action@v3 36 | with: 37 | username: ${{ secrets.DOCKERHUB_USERNAME }} 38 | password: ${{ secrets.DOCKERHUB_TOKEN }} 39 | 40 | - name: Extract metadata (tags, labels) for Docker 41 | id: meta 42 | uses: docker/metadata-action@v5 43 | with: 44 | images: yegle/fava 45 | tags: | 46 | type=raw,value=latest,enabled={{is_default_branch}} 47 | type=ref,event=branch,enabled={{!is_default_branch}} 48 | type=ref,event=pr 49 | 50 | - name: Build and push Docker image 51 | uses: docker/build-push-action@v6 52 | with: 53 | context: . 54 | platforms: linux/amd64,linux/arm64 55 | push: ${{ github.event_name != 'pull_request' }} 56 | tags: ${{ steps.meta.outputs.tags }} 57 | labels: ${{ steps.meta.outputs.labels }} 58 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BEANCOUNT_VERSION=2.3.6 2 | ARG FAVA_VERSION=v1.30 3 | 4 | ARG NODE_BUILD_IMAGE=22-bookworm 5 | FROM node:${NODE_BUILD_IMAGE} AS node_build_env 6 | ARG FAVA_VERSION 7 | 8 | WORKDIR /tmp/build 9 | RUN git clone https://github.com/beancount/fava 10 | 11 | RUN apt-get update 12 | RUN apt-get install -y python3-babel 13 | 14 | WORKDIR /tmp/build/fava 15 | RUN git checkout ${FAVA_VERSION} 16 | RUN make 17 | RUN rm -rf .*cache && \ 18 | rm -rf .eggs && \ 19 | rm -rf .tox && \ 20 | rm -rf build && \ 21 | rm -rf dist && \ 22 | rm -rf frontend/node_modules && \ 23 | find . -type f -name '*.py[c0]' -delete && \ 24 | find . -type d -name "__pycache__" -delete 25 | 26 | # Why not use `python:bookworm`? Because the final app is served by 27 | # distroless Python image, which is Debian + Python from Debain APT 28 | # repo. The python intepreter in the `python:bookworm` image is not from 29 | # Debian APT repo. 30 | FROM debian:bookworm AS build_env 31 | ARG BEANCOUNT_VERSION 32 | 33 | RUN apt-get update 34 | RUN apt-get install -y build-essential libxml2-dev libxslt-dev curl \ 35 | python3 libpython3-dev python3-pip git python3-venv 36 | 37 | 38 | ENV PATH="/app/bin:$PATH" 39 | RUN python3 -mvenv /app 40 | COPY --from=node_build_env /tmp/build/fava /tmp/build/fava 41 | 42 | WORKDIR /tmp/build 43 | RUN git clone https://github.com/beancount/beancount 44 | 45 | WORKDIR /tmp/build/beancount 46 | RUN git checkout ${BEANCOUNT_VERSION} 47 | 48 | RUN CFLAGS=-s pip3 install -U /tmp/build/beancount 49 | RUN pip3 install -U /tmp/build/fava 50 | ADD requirements.txt . 51 | RUN pip3 install --require-hashes -U -r requirements.txt 52 | RUN pip3 install git+https://github.com/beancount/beanprice.git@41576e2ac889e4825e4985b6f6c56aa71de28304 53 | RUN pip3 install git+https://github.com/andreasgerstmayr/fava-portfolio-returns.git@de68b54f3ac517adfde3a4ccb41fdb09a0da41d1 54 | 55 | RUN pip3 uninstall -y pip 56 | 57 | RUN find /app -name __pycache__ -exec rm -rf -v {} + 58 | 59 | FROM gcr.io/distroless/python3-debian12 60 | COPY --from=build_env /app /app 61 | 62 | # Default fava port number 63 | EXPOSE 5000 64 | 65 | ENV BEANCOUNT_FILE="" 66 | 67 | ENV FAVA_HOST="0.0.0.0" 68 | ENV PATH="/app/bin:$PATH" 69 | 70 | ENTRYPOINT ["fava"] 71 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yuchen Ying 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 | # fava-docker 2 | 3 | A Dockerfile for [beancount-fava](https://github.com/beancount/fava), 4 | now the default web UI for [beancount](https://github.com/beancount/beancount). 5 | 6 | ## Usage Example 7 | 8 | You can get started creating a container from this image, you can either use docker-compose or the docker cli. 9 | 10 | Assuming you have `example.bean` in the current directory: 11 | 12 | ### Docker Cli 13 | 14 | ```bash 15 | docker run -d \ 16 | --name=syncthing \ 17 | -v $PWD:/bean \ 18 | -e BEANCOUNT_FILE=/bean/example.bean \ 19 | -p 5000:5000 \ 20 | --restart unless-stopped \ 21 | yegle/fava 22 | ``` 23 | 24 | ### Docker Compose 25 | 26 | ```yml 27 | --- 28 | version: "3.0" 29 | services: 30 | fava: 31 | container_name: fava 32 | image: yegle/fava 33 | ports: 34 | - 5000:5000 35 | environment: 36 | - BEANCOUNT_FILE=/bean/example.beancount 37 | volumes: 38 | - ${PWD}/:/bean 39 | restart: unless-stopped 40 | ``` 41 | 42 | ## Environment Variable 43 | 44 | | Parameter | Value | 45 | | :----: | --- | 46 | | `BEANCOUNT_FILE` | path to your beancount file. Default to empty string. | 47 | 48 | ## Note on auto build 49 | 50 | The [docker image](https://hub.docker.com/r/yegle/fava) was switched 51 | from build by Docker Hub to Github Actions. The image label pattern is 52 | changed: instead of labeled `version-1.xx` it's now labeled `v1.xx`. 53 | 54 | You can check the auto build logs at https://github.com/yegle/fava-docker/actions. 55 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beancount-reds-plugins==0.3.0 --hash=sha256:4bf7d56a9d8084acb50591283cf1724eaa035c2f2df2ac70a3370b0d068da6ed 2 | fava-plugins==1.0 --hash=sha256:3668ccd1fdf3f744ef9e65c2972e1e6cf20ad34e6f25f86a1828fe7ce8fcf13b 3 | 4 | # Manual specification of python-dateutil is a workaround for https://github.com/yegle/fava-docker/issues/39 5 | python-dateutil==2.8.2 --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9 6 | beancount-periodic==0.1.2 --hash=sha256:352fc97be1ff17351d62273b00a41a1f454f3bc6b4b7e88d6d4b08854fe3a412 7 | 8 | -------------------------------------------------------------------------------- /update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | err() { echo $1; exit 1; } 4 | 5 | if [[ $# -ne 0 ]] 6 | then 7 | BEANCOUNT_VERSION=$1 8 | else 9 | BEANCOUNT_VERSION=$(git branch --show-current) 10 | fi 11 | 12 | if [[ ${BEANCOUNT_VERSION} == "master" ]] 13 | then 14 | err "Please run update in a non-master branch" 15 | elif ! [[ ${BEANCOUNT_VERSION} =~ v.* ]] 16 | then 17 | err "Beancount version should be a string matching /v.*/, got '${BEANCOUNT_VERSION}'" 18 | fi 19 | 20 | BEANCOUNT_VERSION=${BEANCOUNT_VERSION#v} 21 | 22 | REQUIREMENTS_TXT="https://raw.githubusercontent.com/beancount/beancount/${BEANCOUNT_VERSION}/requirements.txt" 23 | 24 | TMPFILE=$(mktemp) 25 | 26 | curl -o ${TMPFILE} ${REQUIREMENTS_TXT} 27 | 28 | pip-compile ${TMPFILE} 29 | --------------------------------------------------------------------------------