Your lists, in plain files, one tap away.

Natlas is a small self-hosted app for the lists that run your life: todos and reminders, bills and renewals, birthdays, races and workouts, the things you own. It installs on your phone like an app, and every list is a plain YAML or CSV file you can open in any editor, sync with your notes and change with your own scripts.

Sign in Install your own Source code

This is a private instance - there is no public demo. Natlas is free and open source; the steps below get your own copy running in about five minutes.

☀️ Today screen

Overdue, today and the next 7 days from every list in one place, each with a Done button - plus the numbers that matter, like monthly spend or birthdays this week.

✅ One tap

Done, Tomorrow, Cancel, Completed: each list has its own buttons. Tap a badge such as open or high to change it without opening a form.

👆 Swipe & select

Swipe right for the main action, left to edit. Long-press to select many rows and close, reschedule or re-tag them together.

↩️ Undo everything

Every change - an edit, a Done, a delete, a bulk update - shows an Undo button for a few seconds. Mis-taps cost nothing.

⚡ Quick add

Type Call bank tomorrow #todo !high and the title, date, tag and priority are filled in for you. Weekdays, “12 oct”, “25/12” and “+3” all work.

🧩 Plugins in YAML

A list is one plugin.yml: fields, dropdowns, sections, buttons, Today rules and totals. Add a new kind of list without writing code.

📄 Plain files

No database. Each list is one human-readable file next to your notes - greppable, diffable, and safe to edit by hand or from Obsidian.

🤝 Script-friendly

Only the record you change is rewritten. Comments, unknown fields and file order are kept, and an edit that would overwrite a script's change is refused.

📱 Installable app

Bottom navigation, full-screen forms and shortcuts on your phone; a side panel on the desktop. Opens offline with your last data, read-only.

🔒 Private by design

Single user, hashed password, signed cookies, login lockout, strict browser policies, no third-party requests and no tracking.

🪶 Tiny

One Go binary of about 8 MB with the web app built in. Runs in a 10 MB Docker image as your own user, with a read-only filesystem.

🌗 Light & dark

The nss teal theme follows your system, with a manual switch. Large tap targets, readable on any screen.

Lists it ships with

ListFileWhat you get
📅 Eventsevent.mdTodos and reminders grouped Overdue / Today / Next 7 days / Later / Closed. Done, Tomorrow and Reopen buttons, smart quick add.
💳 Subscriptionssubscription.mdBills and renewals, “due in 7 days”, and true monthly spend across any cycle: monthly, yearly, “84 days”, “3 years”.
🎁 Birthdaysbirthdays.mdRead-only, sorted by the next birthday, with the age each person turns.
❤️ Healthhealth.mdPlanned races (a Completed button asks for time and cost and moves the race to history), race history, gym routine in your own order, and a profile.
📦 Inventoryinventory.csvWhat you own, its condition and where it is kept, with value and weight totals by place, type or condition.

Turn lists on or off and choose their order in natlas.yml. Each one is just a folder under plugins/.

What it stores

An event is a few plain lines. Natlas, your editor and your scripts can all read and write it.

updated: 2026-10-07 02:10:00

events:
- id: renew-passport
  title: Renew passport
  date: '2026-10-12'
  status: open          # open | pending | closed
  priority: high        # high | medium | low
  tags: todo
  frequency: none       # none | daily | weekly | monthly | quarterly | yearly
  note: Book a slot first

Install

You need a Linux box with Docker and a reverse proxy that serves HTTPS (Caddy, Traefik, nginx, or a Cloudflare tunnel).

  1. Get the code

    git clone https://gitlab.com/niharokz/natlas.git
    cd natlas
    cp .env.example .env
    cp natlas.example.yml natlas.yml
  2. Set your login

    Build the image, then create a password hash and a secret key and paste both into .env:

    docker compose build
    read -rs PW && printf '%s' "$PW" | docker run --rm -i natlas:local hash-password
    openssl rand -hex 32        # → NATLAS_SECRET_KEY

    Also set NATLAS_USERNAME, NATLAS_DATA_DIR (the folder with your files), TZ, PUID/PGID (from id -u / id -g) and NATLAS_NETWORK (the Docker network your proxy is on).

  3. Check and start

    docker compose run --rm natlas check     # lists every config mistake, with file and key
    docker compose up -d --build
    docker logs natlas                       # "natlas … listening on :8080"

    Missing data files are created on the first save; examples/data/ has samples of every format.

  4. Put it behind HTTPS

    natlas.example.com {
        reverse_proxy natlas:8080
    }

    Open the site, sign in, then use your browser's Install / Add to Home Screen.

  5. Update later

    git pull && docker compose up -d --build

    Edited a plugin.yml or natlas.yml? docker compose restart natlas is enough.

Just looking? With Go 1.24+, sh scripts/dev.sh runs it on http://localhost:8080 against a throwaway copy of the example data (login demo / demo).

Configure

.env - secrets and machine

NATLAS_USERNAME
the single login
NATLAS_PASSWORD_HASH
from natlas hash-password (or a plain NATLAS_PASSWORD)
NATLAS_SECRET_KEY
signs the session cookie, 32+ characters
NATLAS_DATA_DIR
host folder with your list files
TZ · PUID · PGID
timezone for “today”; the user that owns the files
NATLAS_NETWORK
Docker network shared with your proxy

natlas.yml - the app

title: Natlas
currency: "₹"
locale: en-IN
data_dir: /data
plugins:            # menu order
  - events
  - subscriptions
  - birthdays
  - health
  - inventory
lists:              # shared dropdowns
  rooms: [Kitchen, Bedroom]

Add your own list

Create plugins/books/plugin.yml, add books to natlas.yml, restart. That is the whole plugin:

title: Books
icon: box
file: books.md
collections:
  - id: books
    path: books
    fields:
      - { key: title, type: text, required: true }
      - { key: author, type: select, options: data }      # learns values from your file
      - { key: status, type: select, options: [to read, reading, read], default: to read }
      - { key: finished, type: date }
    list:
      title: title
      subtitle: [author, finished]
      badges: [status]                                    # tap to change in place
      groups:
        - { label: Reading, where: { status: reading } }
        - { label: Read, where: { status: read }, sort: [-finished], collapsed: true }
    actions:
      - { id: finish, label: Finished, primary: true, set: { status: read, finished: today } }
    quick_add: { title: title }
    widgets:
      - { label: Reading, count: { status: reading } }

Field types: text, textarea, number, money, date, select, bool, list, url and anniversary. Conditions use eq ne in lt lte gt gte empty contains plus any / all / not, with dates like today+7. The README documents every option.

Security