Sign inSign up

fsalfnerdev/ibuttoncycle-api

By fsalfnerdev

•Updated 4 months ago

The Core Image of the ibuttoncycle server

Image
0

862

fsalfnerdev/ibuttoncycle-api repository overview

⁠iButtonCycle Server - API Backend

The app iButtonCycle⁠ can store all data on a self-hosted server.

The easiest way to run the iButtonCycle Server is with the Traefik⁠ edge router / reverse proxy.

The iButtonCycle Server setup requires to run four Docker containers

  1. API Backend⁠
  2. The Admin Web Frontend⁠
  3. A pre-configured MariaDB database⁠
  4. A backup container⁠

This image provides the API Backend

⁠Changelog

⁠2025/12/06
  • This version ships with a new web frontend built with Flutter instead of React. Important note: The new web frontend only supports administration tasks such as adding users. Regular usage (like making day entries or showing charts) haven been removed - data can only be entered via the Android or iOS app. If you need a web frontend with data entry functionality, you need to use the images tagged with v1.0 but be aware that this version is no longer maintained (in fact it contains several packages that have critical vulnerabilities).
  • When running the new image behind a traefik reverse proxy, traefik encountered issues with renewing the Let's encrypt certificates.
  • Sending a 308 Permanent Redirect from ibuttoncycle.<your-domain> to ibuttoncycle-api.<your-domain> is no longer included in the Admin Web Frontend docker image. You need to setup the redirect yourself. If you use the traefik reverse proxy, you can find the according labels in the docker-compose.yaml file below
  • We encountered some issues with redirects in WebKit browsers (Safari, all browsers on iOS, etc.). When resending a POST request after receiving a 308 Permanent Redirect, the Content-Type HTTP header was not sent with the second request. Our solution is to add the Content-Type header in the reverse proxy when forwarding the request to the API backend docker container. If you use the Traefik reverse proxy, the docker-compose.yaml file below includes a middleware to do that.
  • The API now includes a system to manage database migrations. After pulling the new Docker image run the following command: docker exec -it ibuttoncycle-api vendor/bin/phinx migrate -e production. The database migration tool requires extra environment variables: PHINX_DB_HOST, PHINX_DB_NAME, PHINX_DB_USER, and PHINX_DB_PASSWORD. They can be added via the docker-compose.yaml file as shown below. If you setup the server for the first time, you do not need to migrate.

⁠Table of Contents

⁠Setup your Traefik edge router

The compose setup is intended to run with traefik as an edge router / reverse proxy. In case you already have already another traefik instance running, some adaptions might be necessary.

⁠Create the Gateway Network

The iButtonCycle setup involves two networks

  • An internal network called ibuttoncycle
  • A gateway network called web

The web network is set up as external network, i.e. it is assumed to exist. To create the network use the command docker network create web

If you already have an "outward facing" network defined, adapt the configuration accordingly.

⁠Create the Security headers Middleware for Traefik

The iButtonCycle setup depends on a middleware to add Security headers. Create a file named traefik_dynamic_conf.yml with the following content:

tls:
  options:
    default:
      minVersion: VersionTLS12
      cipherSuites:
        - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
        - TLS_AES_128_GCM_SHA256
        - TLS_AES_256_GCM_SHA384
        - TLS_CHACHA20_POLY1305_SHA256
      curvePreferences:
        - CurveP521
        - CurveP384
      sniStrict: true

http:
  middlewares:
    secHeaders:
      headers:
         browserXssFilter: true
         contentTypeNosniff: true
         frameDeny: true
         stsIncludeSubdomains: true
         stsPreload: true
         stsSeconds: 31536000
         customFrameOptionsValue: "SAMEORIGIN"

The iButtonCycle docker compose setup (see next section) refrences the middleware via traefik.http.routers.ibuttoncyclewebadmin.middlewares=secHeaders@file

In case you are running your own Edge Router setup, make sure that you have a secHeaders Traefik middleware defined.

⁠Start the Traefik edge router

IMPORTANT the setup uses Let's encrypt⁠ to generate TLS certificates for the server. Let's encrypt requires a proper email address, which you need to store in a .env file.

To do so, create a file traefik/.env with the following content:

LETSENCRYPT_EMAIL=<[email protected]>

Configure the Traefik router with a docker-compose.yaml setup as follows:

networks:
  web:
    external: true

services:
  router:
    image: "traefik:latest"
    container_name: "traefik"
    command:
      - "--log.level=ERROR"
      - "--accesslog=false"
      - "--api=false"
      - "--providers.file.filename=/dynamic_conf.yml"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencryptresolver.acme.email=${LETSENCRYPT_EMAIL}"
      - "--certificatesresolvers.letsencryptresolver.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./letsencrypt:/letsencrypt
      - ./traefik_dynamic_conf.yml:/dynamic_conf.yml
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
    networks:
      - web

Start the traefik router using the command docker compose up -d in the directory in which the three previously created files are located

⁠Create DNS records for the iButtonCycle services

Running an iButtonCycle Server requires two subdomains:

  1. ibuttoncycle.your.domain
  2. ibuttoncycle-api.your.domain

Go to your DNS provider and create DNS records to point the sub-domain to the server on which you are running iButtonCycle. It doesn't matter if you use A or CNAME records, as long as the subdomain resolves into the IP of your server.

⁠Run the iButtonCycle Server as a docker compose setup

Now as you have your edge router running, you can start all four iButtonCycle server containers using the following docker compose setup.

⁠Set passwords in an env file

Create a directory in which you will manage the iButtonCycle Docker compose setup, e.g., ibuttoncycle.

Create a file called .env with the following content:

IBUTTONCYCLE_JWT_SECRET=jwt_secret
IBUTTONCYCLE_DB_ROOT_PWD=db_root_pwd
IBUTTONCYCLE_DB_USER_PWD=db_user_pwd
IBUTTONCYCLE_ADMIN_PWD=ibuttoncycle_admin
IBUTTONCYCLE_DB=ibuttoncycle
IBUTTONCYCLE_DB_USER=ibuttoncycle
IBUTTONCYCLE_BASE_DOMAIN=your.domain

But replace the values of each variable. The variables have the following meaning:

  • IBUTTONCYCLE_DB_ROOT_PWD - the password for the database root user
  • IBUTTONCYCLE_DB_USER - the username of the ibuttoncycle database user
  • IBUTTONCYCLE_DB_USER_PWD - the password for the ibuttoncycle database user
  • IBUTTONCYCLE_DB - the name of the database to be used
  • IBUTTONCYCLE_JWT_SECRET - iButtonCycle uses JWT-based authorization, this is the private encryption secret
  • IBUTTONCYCLE_ADMIN_PWD - the password of the admin user of the web administration interface
  • IBUTTONCYCLE_BASE_DOMAIN - the hosts of ibuttoncycle will be established as subdomains, e.g., ibuttoncycle.org
⁠Create a docker volume used for backup

The Docker compose setup listed below relies on an "external" docker volume named ibuttoncycle_backup where the backup Docker container writes DB backups to. "external" means that the Docker volume will not be created from the docker-compose file but needs to be created separately. For example, if you want to store backups on an NFS share, you can create the volume by

docker volume create --driver local --opt type=nfs --opt o=addr=192.168.1.1,rw --opt device=:/path/to/dir ibuttoncycle_backup

where 192.168.1.1 is the IP address of your NFS server and /path/to/dir is the path of the NFS share.

⁠Configure the Docker compose setup

To do so, create a directory ibuttoncycle and in that directory a docker-compose.yaml with the following content:

# the following environment variables need to be available:
#   - IBUTTONCYCLE_DB_ROOT_PWD - the password for the database root user
#   - IBUTTONCYCLE_DB_USER - the username of the ibuttoncycle database user
#   - IBUTTONCYCLE_DB_USER_PWD - the password for the ibuttoncycle database user
#   - IBUTTONCYCLE_DB - the name of the database to be used
#   - IBUTTONCYCLE_JWT_SECRET - iButtonCycle uses JWT-based authorization, this is the private encryption secret
#   - IBUTTONCYCLE_ADMIN_PWD - the password of the admin user of the web administration interface
#   - IBUTTONCYCLE_BASE_DOMAIN - the hosts of ibuttoncycle will be established as subdomains, e.g., ibuttoncycle.{IBUTTONCYCLE_BASE_DOMAIN}
#
# the environment variables can be provided in a .env file in the same directory as this docker.compose.yaml file
#
# the following domain names need to be resolvable (have a DNS record)
#   - ibuttoncycle-api.${IBUTTONCYCLE_BASE_DOMAIN}
#   - ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}

networks:
  web:
    external: true
  ibuttoncycle:
    external: false

volumes:
  ibuttoncycle_db:
  ibuttoncycle_backup:
    external: true

services:
  ibuttoncycle-db:
    image: fsalfnerdev/ibuttoncycle-db:prod
    container_name: "ibuttoncycle-db"
    restart: always
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - ibuttoncycle_db:/var/lib/mysql
    networks:
      - ibuttoncycle
    environment:
      - MARIADB_ROOT_PASSWORD=${IBUTTONCYCLE_DB_ROOT_PWD}
      - MYSQL_PASSWORD=${IBUTTONCYCLE_DB_USER_PWD}
      - MYSQL_DATABASE=${IBUTTONCYCLE_DB}
      - MYSQL_USER=${IBUTTONCYCLE_DB_USER}
      - MYSQL_INITDB_SKIP_TZINFO=1
  
  ibuttoncycle-backup:
    image: fsalfnerdev/ibuttoncycle-backup:prod
    container_name: "ibuttoncycle-backup"
    restart: always
    depends_on:
      - ibuttoncycle-db
    volumes:
      - ibuttoncycle_backup:/db_backup
    networks:
      - ibuttoncycle
    environment:
      - DB=${IBUTTONCYCLE_DB}
      - DB_HOST=ibuttoncycle-db
      - DB_ROOT_PASSWD=${IBUTTONCYCLE_DB_ROOT_PWD}

  ibuttoncycle-api:
    image: fsalfnerdev/ibuttoncycle-api:prod
    container_name: "ibuttoncycle-api"
    restart: always
    networks:
      - ibuttoncycle
      - web
    depends_on:
      - ibuttoncycle-db
    environment:
      - DB=${IBUTTONCYCLE_DB}
      - DB_USER=${IBUTTONCYCLE_DB_USER}
      - DB_HOST=ibuttoncycle-db
      - DB_PASSWD=${IBUTTONCYCLE_DB_USER_PWD}
      - JWT_SECRET=${IBUTTONCYCLE_JWT_SECRET}
      - ADMIN_PWD=${IBUTTONCYCLE_ADMIN_PWD}
      - ADMIN_URL=ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}
      - PHINX_DB_HOST=ibuttoncycle-db
      - PHINX_DB_NAME=${IBUTTONCYCLE_DB}
      - PHINX_DB_USER=${IBUTTONCYCLE_DB_USER}
      - PHINX_DB_PASSWORD=${IBUTTONCYCLE_DB_USER_PWD}
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=web"

      - "traefik.http.middlewares.api-add-prefix.addprefix.prefix=index.php"

      - "traefik.http.middlewares.admin-add-prefix.addprefix.prefix=admin.php"

      - "traefik.http.middlewares.ibuttoncyclecorsheaders.headers.accessControlAllowMethods=GET,OPTIONS,PUT,PATCH"
      - "traefik.http.middlewares.ibuttoncyclecorsheaders.headers.accessControlAllowOriginList=https://ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}"
      - "traefik.http.middlewares.ibuttoncyclecorsheaders.headers.accessControlAllowCredentials=true"
      - "traefik.http.middlewares.ibuttoncyclecorsheaders.headers.accessControlAllowHeaders=content-type,authorization"

      # we need to add content-type headers since WebKit drops headers after a redirect
      - "traefik.http.middlewares.ibuttoncycle-add-contenttype.headers.customRequestHeaders.Content-Type=application/json"

      - "traefik.http.routers.ibuttoncycleapi.rule=Host(`ibuttoncycle-api.${IBUTTONCYCLE_BASE_DOMAIN}`) && PathPrefix(`/api`)"
      - "traefik.http.routers.ibuttoncycleapi.entrypoints=websecure"
      - "traefik.http.routers.ibuttoncycleapi.tls.certresolver=letsencryptresolver"
      - "traefik.http.routers.ibuttoncycleapi.middlewares=secHeaders@file,api-add-prefix@docker,ibuttoncyclecorsheaders@docker,ibuttoncycle-add-contenttype@docker"

      - "traefik.http.routers.ibuttoncycleadmin.rule=Host(`ibuttoncycle-api.${IBUTTONCYCLE_BASE_DOMAIN}`) && PathPrefix(`/admin`)"
      - "traefik.http.routers.ibuttoncycleadmin.entrypoints=websecure"
      - "traefik.http.routers.ibuttoncycleadmin.tls.certresolver=letsencryptresolver"
      - "traefik.http.routers.ibuttoncycleadmin.middlewares=secHeaders@file,admin-add-prefix@docker,ibuttoncyclecorsheaders@docker,ibuttoncycle-add-contenttype@docker"

  ibuttoncycle:
    image: fsalfnerdev/ibuttoncycle-web:prod
    container_name: "ibuttoncycle-webadmin"
    restart: always
    networks:
      - ibuttoncycle
      - web
    depends_on:
      - ibuttoncycle-db
      - ibuttoncycle-api
    environment:
      - API_URL=ibuttoncycle-api.${IBUTTONCYCLE_BASE_DOMAIN}
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=web"
      - "traefik.http.routers.ibuttoncyclewebadmin.rule=Host(`ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}`)"
      - "traefik.http.routers.ibuttoncyclewebadmin.entrypoints=websecure"
      - "traefik.http.routers.ibuttoncyclewebadmin.tls.certresolver=letsencryptresolver"
      - "traefik.http.routers.ibuttoncyclewebadmin.middlewares=secHeaders@file"

      # redirect all routes with the /admin prefix to ibuttoncycle-api.ibuttoncycle.org/admin
      - "traefik.http.middlewares.ibuttoncycle-admin-redirect.redirectRegex.regex=^https://ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}/admin/(.*)"
      - "traefik.http.middlewares.ibuttoncycle-admin-redirect.redirectRegex.replacement=https://ibuttoncycle-api.${IBUTTONCYCLE_BASE_DOMAIN}/admin/${1}"
      - "traefik.http.middlewares.ibuttoncycle-admin-redirect.redirectRegex.permanent=true"
      - "traefik.http.routers.ibuttoncycle-admin-redirect.rule=Host(`ibuttoncycle.${IBUTTONCYCLE_BASE_DOMAIN}`) && PathPrefix(`/admin`)"
      - "traefik.http.routers.ibuttoncycle-admin-redirect.middlewares=ibuttoncyclecorsheaders@docker,ibuttoncycle-admin-redirect@docker,secHeaders@file"
      - "traefik.http.routers.ibuttoncycle-admin-redirect.entrypoints=websecure"
      - "traefik.http.routers.ibuttoncycle-admin-redirect.tls.certresolver=letsencryptresolver"
⁠Run the iButtonCycle Server

To start the Docker compose setup for the iButtonCycleServer, run

docker compose up -d

⁠Test setup

Please note that the servers need some time to set up and initialize the DB (even after docker compose says "done").

To test if the server is running,

  1. go to https://ibuttoncycle.your.domain.
  2. Type in the admin password (the one saved under IBUTTONCYCLE_ADMIN_PWD) into the bottom input field
  3. You should see the interface with which you can manage iButtonCycle App users

For more details, on how to use the admin interface, please continue reading at (https://ibuttoncycle.org/einen-eigenen-server-aufsetzen.html⁠)

⁠Setup iButtonCycle as a Systemd Service

In case you run the iButtonCycle server on a Linux system that uses systemd, you can configure the iButtonCycle service with the following unit file called ibuttoncycle.service

[Unit]
Description=iButtonCycle docker compose setup
Requires=docker.service
After=docker.service

[Service]
Restart=always
User=root
Group=docker
WorkingDirectory=/etc/opt/containers/ibuttoncycle
ExecStartPre=/usr/bin/docker compose -f docker-compose.yaml down
ExecStart=/usr/bin/docker compose -f docker-compose.yaml up
ExecStartPre=/usr/bin/docker compose -f docker-compose.yaml down

[Install]
WantedBy=multi-user.target

Tag summary

Content type

Image

Digest

sha256:4eebd1221…

Size

174.3 MB

Last updated

4 months ago

docker pull fsalfnerdev/ibuttoncycle-api:prod