Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/ptshen/timeful-plus/llms.txt

Use this file to discover all available pages before exploring further.

Podman is a daemonless, rootless-by-default alternative to Docker. Timeful’s hardened container images work seamlessly with Podman — the same fixed UIDs (backend 1000, frontend 101, MongoDB 999) are automatically remapped into your user’s subUID range, so volume permissions just work. You can start quickly with podman-compose, or graduate to Podman Quadlets for full systemd integration with dependency ordering, auto-restart, and centralised journal logging.
Timeful requires Podman 4.4 or later for Quadlet support. For Podman Compose only, any recent Podman release is sufficient.

Quick Start with Podman Compose

Podman Compose reads the same docker-compose.yml files as Docker Compose, so you can drop in a replacement with zero config changes.
# Install podman-compose (requires Python 3)
pip3 install podman-compose

# Start the full stack (MongoDB + backend + frontend)
podman-compose up -d

# Follow logs
podman-compose logs -f

# Stop the stack
podman-compose down
To use the pre-built GHCR images instead of building from source:
podman-compose -f docker-compose.ghcr.yml up -d
All security hardening — non-root users, no-new-privileges, capability dropping — is declared in the Compose files and takes effect automatically with Podman.

Systemd Quadlets

Quadlets are .container, .network, and .volume unit files that Podman translates into native systemd service units at daemon-reload time. This gives you standard systemctl management, boot-time startup, and journald logging for every Timeful container.

File Locations

ScopeDirectory
Per-user (rootless, recommended)~/.config/containers/systemd/
System-wide (requires root)/etc/containers/systemd/

Step-by-Step Setup

1

Create the quadlet directory

mkdir -p ~/.config/containers/systemd
2

Build the local images

Before the services can start, build the backend and frontend images:
cd /path/to/timeful.app
podman build -f Dockerfile.backend -t localhost/timeful-backend:latest .
podman build -f Dockerfile.frontend -t localhost/timeful-frontend:latest .
3

Create the network unit

~/.config/containers/systemd/timeful.network:
[Network]
NetworkName=timeful
Driver=bridge
4

Create the MongoDB container unit

~/.config/containers/systemd/timeful-mongodb.container:
[Unit]
Description=Timeful MongoDB Database
After=network-online.target
Wants=network-online.target

[Container]
Image=docker.io/library/mongo:6.0
ContainerName=timeful-mongodb
AutoUpdate=registry

User=999:999

Environment=MONGO_INITDB_DATABASE=schej-it

Network=timeful.network
# Remove PublishPort in production to keep MongoDB off the host network
PublishPort=27017:27017

Volume=timeful-mongodb-data:/data/db
Volume=timeful-mongodb-config:/data/configdb

SecurityLabelDisable=false
NoNewPrivileges=true

HealthCmd=mongosh --eval "db.adminCommand('ping')" --quiet
HealthInterval=10s
HealthTimeout=5s
HealthRetries=5

[Service]
Restart=unless-stopped
TimeoutStartSec=900

[Install]
WantedBy=default.target
5

Create the backend container unit

~/.config/containers/systemd/timeful-backend.container:
[Unit]
Description=Timeful Backend API Server
After=timeful-mongodb.service
Requires=timeful-mongodb.service
After=network-online.target
Wants=network-online.target

[Container]
Image=localhost/timeful-backend:latest
ContainerName=timeful-backend
AutoUpdate=registry

User=1000:1000

Environment=MONGO_URI=mongodb://timeful-mongodb:27017
Environment=MONGO_DB_NAME=schej-it
Environment=GIN_MODE=release
EnvironmentFile=%h/.config/timeful/backend.env

Network=timeful.network

Volume=timeful-backend-logs:/app/logs

SecurityLabelDisable=false
NoNewPrivileges=true

[Service]
Restart=unless-stopped
TimeoutStartSec=900

[Install]
WantedBy=default.target
6

Create the frontend container unit

~/.config/containers/systemd/timeful-frontend.container:
[Unit]
Description=Timeful Frontend Web Server
After=timeful-backend.service
Requires=timeful-backend.service
After=network-online.target
Wants=network-online.target

[Container]
Image=localhost/timeful-frontend:latest
ContainerName=timeful-frontend
AutoUpdate=registry

User=101:101

Network=timeful.network
PublishPort=3002:80

Environment=BACKEND_HOST=timeful-backend
Environment=BACKEND_PORT=3002

SecurityLabelDisable=false
NoNewPrivileges=true

[Service]
Restart=unless-stopped
TimeoutStartSec=900

[Install]
WantedBy=default.target
7

Create the volume units

Named volumes referenced in the container units need matching .volume files so Podman manages them as quadlet resources.~/.config/containers/systemd/timeful-mongodb-data.volume:
[Volume]
VolumeName=timeful-mongodb-data
~/.config/containers/systemd/timeful-mongodb-config.volume:
[Volume]
VolumeName=timeful-mongodb-config
~/.config/containers/systemd/timeful-backend-logs.volume:
[Volume]
VolumeName=timeful-backend-logs
8

Create the environment file

The backend unit reads secrets from ~/.config/timeful/backend.env. Create it with restricted permissions:
mkdir -p ~/.config/timeful
cat > ~/.config/timeful/backend.env << 'EOF'
ENCRYPTION_KEY=<output of: openssl rand -base64 32>
CLIENT_ID=
CLIENT_SECRET=
MICROSOFT_CLIENT_ID=
MICROSOFT_CLIENT_SECRET=
GMAIL_APP_PASSWORD=
SCHEJ_EMAIL_ADDRESS=
SELF_HOSTED_PREMIUM=true
BASE_URL=
CORS_ALLOWED_ORIGINS=
EOF

chmod 600 ~/.config/timeful/backend.env
9

Reload systemd and start services

# Regenerate units from quadlet files
systemctl --user daemon-reload

# Enable services to start at login
systemctl --user enable timeful-mongodb.service
systemctl --user enable timeful-backend.service
systemctl --user enable timeful-frontend.service

# Start now
systemctl --user start timeful-mongodb.service
systemctl --user start timeful-backend.service
systemctl --user start timeful-frontend.service
10

(Optional) Enable linger

By default, user systemd services stop when you log out. Enable linger to keep Timeful running even when no session is active:
loginctl enable-linger $USER

Managing Quadlet Services

# Check status
systemctl --user status timeful-mongodb.service
systemctl --user status timeful-backend.service
systemctl --user status timeful-frontend.service

# Follow logs via journald
journalctl --user -u timeful-backend.service -f
journalctl --user -u timeful-mongodb.service -f

# Restart a service
systemctl --user restart timeful-backend.service

# Stop all services
systemctl --user stop timeful-frontend.service
systemctl --user stop timeful-backend.service
systemctl --user stop timeful-mongodb.service

# List all timeful units
systemctl --user list-units "timeful-*"

Updating

# Stop the backend and frontend
systemctl --user stop timeful-frontend.service timeful-backend.service

# Pull latest source and rebuild
cd /path/to/timeful.app
git pull
podman build -f Dockerfile.backend -t localhost/timeful-backend:latest .
podman build -f Dockerfile.frontend -t localhost/timeful-frontend:latest .

# Restart services
systemctl --user start timeful-backend.service timeful-frontend.service

Differences from Docker

AspectDockerPodman
DaemonRequires dockerdDaemonless — containers are direct child processes
Default userroot (unless rootless mode configured)Your own UID (rootless by default)
UID mappingOptional, needs explicit setupAutomatic via /etc/subuid and /etc/subgid
Composedocker compose (built-in)podman-compose (separate install via pip)
systemd integrationRequires external toolingNative via Quadlets (Podman 4.4+)
Port < 1024Needs root or CAP_NET_BIND_SERVICEWorks unprivileged via slirp4netns
SELinuxOptionalIntegrated where available

UID/GID Namespace Mapping

When Podman runs a container as UID 1000 inside a rootless user session, that UID is mapped to an unprivileged UID in your subuid range on the host. The net effect is:
  • Container processes cannot affect host files owned by other users
  • Named volumes created by containers appear owned by your user on the host
  • No chown workarounds are needed — Timeful’s fixed UIDs work transparently
To verify your subuid allocation:
cat /etc/subuid   # should contain a line like: youruser:100000:65536
podman unshare id # shows UID 0 inside the user namespace

System-wide Installation (Rootful)

To run Timeful as system services accessible to all users, place the quadlet files in /etc/containers/systemd/ and use systemctl without --user:
sudo cp timeful*.container timeful.network /etc/containers/systemd/
sudo systemctl daemon-reload
sudo systemctl enable --now timeful-mongodb.service timeful-backend.service timeful-frontend.service
Rootful Podman requires sudo and runs containers as root on the host. Rootless user services are recommended for most deployments.

Docker Deployment

Deploy with Docker Compose — pre-built images or build from source

Configuration Reference

All .env variables and config.js options explained

Production Setup

Reverse proxy, custom domain, and HTTPS configuration

Security

Container hardening, secrets management, and audit logging

Build docs developers (and LLMs) love