Tandem

Tandem

Docker app from crywolf203's Repository

Overview

Tandem is a self-hosted co-parenting app: a shared parenting-time calendar with swap requests both parents must accept, permanent tamper-evident messages with read receipts, shared expenses with automatic splits, school and district calendar subscriptions (iCal/RSS) with days off flagged, a child profile with insurance cards and care team, a document library for the parenting plan and records, optional voice calls between parents with recording when both agree, an exchange log, a private journal, and printable court-ready reports. Installs on iPhone and Android from the browser like a native app. All data lives in /data (one SQLite file plus uploaded files).

Tandem icon

Unraid setup guide for Tandem, a self-hosted co-parenting app.


Overview

Tandem keeps what two co-parents share in one place on your own server:

  • parenting-time calendar, with swap requests both parents must accept;
  • permanent, tamper-evident messages;
  • shared expenses with receipts;
  • school calendars;
  • the child's health cards and documents;
  • optional voice calls, recorded only when both parents agree.

It installs on iPhone and Android from the browser like a native app.

ghcr.io/crywolf203/tandem:latest
        │
        │ One container · one SQLite file in /data
        ▼
Tandem web app on port 4380
        │
        │ HTTPS via your reverse proxy (needed to install it on phones)
        ▼
iPhone / Android home-screen app

The full feature list, every setting and troubleshooting are in the Tandem README. This guide covers the Unraid side.


Install with the template

  1. Apps → search Tandem → Install.

    If it isn't listed yet, add this template repository instead: Settings → Docker → Template repositories → https://github.com/crywolf203/unraid-templates, then Docker → Add Container → Tandem.

  2. Check Data (/mnt/user/appdata/tandem by default).

  3. Set Public URL if you'll use Tandem away from home, e.g. https://tandem.example.com.

  4. Apply, then open the WebUI. The first person to open Tandem creates the family and invites the other parent from More → Invite someone.

Container path Default host path Purpose
/data /mnt/user/appdata/tandem tandem.db (accounts, schedule, messages, expenses, calls) and files/ (documents, card photos, receipts, call recordings). Back this folder up.
Container port Default Purpose
4380 4380 Web app and API

HTTPS and phones

Phones only install Tandem as an app over HTTPS. Point your reverse proxy at the container:

Proxy Forward to
Nginx Proxy Manager http://UNRAID-IP:4380
SWAG http://UNRAID-IP:4380
Cloudflare Tunnel http://UNRAID-IP:4380

Then set Public URL to that address and Behind a reverse proxy (TRUST_PROXY) to true.


Settings in the template

The template shows the everyday settings; the rest are under Show more settings.

Group Settings Notes
Access PUBLIC_URL, ALLOW_SIGNUPS, TRUST_PROXY, SECURE_COOKIES, SESSION_DAYS Sign-up is invite-only after the first family by default.
Files PUID, PGID, UMASK, TZ Keep 99 / 100 on Unraid.
School calendars FEED_REFRESH_HOURS, ALLOW_PRIVATE_FEEDS Leave ALLOW_PRIVATE_FEEDS=false unless a calendar file is on your LAN.
Weather WEATHER_PROVIDER, WEATHER_UNITS, ZIP_CENTROIDS_FILE auto uses the National Weather Service in the US.
Receipt reading RECEIPT_AI_URL, RECEIPT_AI_MODEL, RECEIPT_AI_KEY Optional. Any OpenAI-compatible vision API, including Ollama on your own server. All blank = off.
Calls LIVEKIT_URL, LIVEKIT_API_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET, RECORDINGS_DIR Optional. Blank LIVEKIT_URL = calls off. See below.

Every setting is described in the Tandem README.


Optional: calls and local AI

Tandem can run extra services next to the app:

  • Calls: LiveKit, Egress for recording, and Redis.
  • Local AI: Ollama for reading receipts, and Whisper for future call transcripts.

These need several containers wired together, so they install with Docker Compose rather than this single template. On Unraid, use the terminal or the Compose Manager plugin.

mkdir -p /mnt/user/appdata/tandem && cd /mnt/user/appdata/tandem
curl -fsSLO https://raw.githubusercontent.com/crywolf203/tandem/main/docker-compose.yml
curl -fsSLO https://raw.githubusercontent.com/crywolf203/tandem/main/setup.sh && chmod +x setup.sh
./setup.sh

setup.sh asks which add-ons to turn on, then:

  • creates the call server's key and secret;
  • picks GPU or CPU (install the Nvidia Driver plugin first if you have a GPU);
  • writes .env.

Then docker compose up -d starts Tandem and the add-ons together.

Add-on data goes in /mnt/user/appdata/tandem-addons. Use Compose or this template for Tandem, not both, so there's only one Tandem container on the data folder.

Calls also need two things outside Unraid; setup.sh prints the exact values:

  1. Reverse proxy: a host name such as calls.example.com → http://UNRAID-IP:7880, with WebSockets on. A Cloudflare Tunnel works for this part.
  2. Router port forwards, straight to Unraid (not through a tunnel): UDP 50000–50100 for call audio, and TCP 7881 as a fallback.

If your internet provider uses CGNAT (no public IP of your own), port forwarding can't work. Run the calls add-on on a small VPS instead.

Recording happens only when both parents turn it on in More → Calls, and both phones show it during the call.


Updating

On the latest tag, Tandem updates through Unraid's normal Check for Updates. Database changes apply automatically on start.

An image update does not add new fields to a template you already saved. When a release adds a setting, it's listed in the release notes. Add it by editing the container, or remove and re-add the container from Apps → Previous Apps to pick up the new template.


Backups

Everything is in the Data folder. Include /mnt/user/appdata/tandem in the Appdata Backup plugin schedule. It stops the container, copies the folder and restarts.


Support

Tandem's reports are factual exports of what's stored. Tandem is not legal advice.

Requirements

For installing on phones and for push-style updates, put Tandem behind HTTPS (e.g. Nginx Proxy Manager, SWAG, or Cloudflare Tunnel) and set PUBLIC_URL and TRUST_PROXY.

Related apps

Details

Repository
ghcr.io/crywolf203/tandem:latest
Last Updated2026-10-26
First Seen2026-10-26

Runtime arguments

Web UI
http://[IP]:[PORT:4380]/
Network
bridge
Shell
sh
Privileged
false
Extra Params
--restart unless-stopped

Template configuration

WebUI PortPorttcp

Port for the web app. Change the host side only if 4380 is already used.

Target
4380
Default
4380
Value
4380
DataPathrw

Holds tandem.db (all accounts, schedules, messages, expenses) and files/ (uploaded documents and card photos). Back this folder up.

Target
/data
Default
/mnt/user/appdata/tandem
Value
/mnt/user/appdata/tandem
Public URLVariable

The address people use to reach Tandem, e.g. https://tandem.example.com. Used to build invite links. When it starts with https://, secure cookies turn on automatically. Leave blank for LAN-only use.

Target
PUBLIC_URL
Time zoneVariable

Default time zone for new families and server logs (IANA name). Each family can change its own time zone in the app.

Target
TZ
Default
America/New_York
Value
America/New_York
Allow open sign-upsVariable

false (recommended): the first person to open Tandem creates the first family; everyone after that needs an invite link. true: anyone who can reach the server can create their own family.

Target
ALLOW_SIGNUPS
Default
false
Value
false
PUIDVariable

User ID that owns files in Data. Unraid default is 99 (nobody).

Default
99
Value
99
PGIDVariable

Group ID that owns files in Data. Unraid default is 100 (users).

Default
100
Value
100
UMASKVariable

File permission mask for new files in Data. 022 lets only the owner write; 002 also lets the group write.

Default
022
Value
022
Behind a reverse proxyVariable

Set to true when Tandem sits behind a reverse proxy, so sign-in rate limiting uses the real visitor IP from X-Forwarded-For.

Target
TRUST_PROXY
Default
false
Value
false
Secure cookiesVariable

Blank = automatic (on when PUBLIC_URL is https). Force with true or false.

Target
SECURE_COOKIES
Session length (days)Variable

How long a device stays signed in.

Target
SESSION_DAYS
Default
60
Value
60
App nameVariable

Name shown on the sign-in screen and in reports.

Target
APP_NAME
Default
Tandem
Value
Tandem
Log requestsVariable

true writes one line per request to the container log. Useful for troubleshooting.

Target
LOG_REQUESTS
Default
false
Value
false
School calendar refresh (hours)Variable

How often linked school and district calendars are downloaded again. 0 turns background refresh off; parents can still refresh a calendar from the app.

Target
FEED_REFRESH_HOURS
Default
6
Value
6
Allow calendar links on private networksVariable

false (recommended) refuses school calendar links that point at private addresses like 192.168.x.x or localhost, so a link can't be used to probe your network. Set true only if you host a calendar file on your own LAN.

Target
ALLOW_PRIVATE_FEEDS
Default
false
Value
false
Weather providerVariable

auto: US National Weather Service for US locations, Open-Meteo elsewhere. nws: US only, free, includes severe-weather alerts. openmeteo: worldwide, free for non-commercial use. off: no weather.

Target
WEATHER_PROVIDER
Default
auto|nws|openmeteo|off
Value
auto
Weather unitsVariable

Temperatures in fahrenheit or celsius.

Target
WEATHER_UNITS
Default
fahrenheit|celsius
Value
fahrenheit
ZIP code table (optional)Variable

Optional file that turns ZIP codes into locations on your server instead of using Open-Meteo's geocoding. Accepts the Census ZCTA gazetteer file or a zip,lat,lon CSV in your appdata folder.

Target
ZIP_CENTROIDS_FILE
Default
/data/zip-centroids.csv
Value
/data/zip-centroids.csv
Receipt reader URL (optional)Variable

OpenAI-compatible API with a vision model, e.g. https://api.openai.com/v1 or a local Ollama server like http://192.168.1.10:11434/v1. Leave blank to turn receipt reading off; receipts can still be attached.

Target
RECEIPT_AI_URL
Receipt reader modelVariable

Vision model name, e.g. gpt-4o-mini, or a local model like qwen2.5vl or llama3.2-vision.

Target
RECEIPT_AI_MODEL
Receipt reader API keyVariable

API key for the receipt reader, if it needs one. Local servers usually don't.

Target
RECEIPT_AI_KEY
Call server address (optional)Variable

Voice calls between parents use a LiveKit server. Address phones use to reach it, e.g. wss://calls.example.com. Blank turns calls off. The easiest way to run LiveKit and recording is the calls add-on: install Tandem with Docker Compose and run ./setup.sh (see the README).

Target
LIVEKIT_URL
Call server API addressVariable

Where Tandem reaches the LiveKit server API, e.g. http://192.168.1.10:7880. Blank = the same host as the call server address.

Target
LIVEKIT_API_URL
Call server API keyVariable

LiveKit API key.

Target
LIVEKIT_API_KEY
Call server API secretVariable

LiveKit API secret (at least 32 characters). Keep it private.

Target
LIVEKIT_API_SECRET
Call recordings folderVariable

Folder inside the container where LiveKit Egress leaves finished recordings. Tandem copies each one into Data with a fingerprint, then removes it. Map a host folder here only if you run Egress yourself.

Target
RECORDINGS_DIR
Default
/recordings
Value
/recordings