Papote
Self-hosted · open source · no Google needed

Your little groups, on your own server.

Papote is a playful messenger for family, friends and flatmates. An Android app, an installable web app for iPhone and computers, and a single Docker container you run at home. Messages, photos, voice notes and drawings never leave it.

Everything from the mockup, and then some

Built from a complete Claude Design mockup: every screen is in the apps.

Chat that feels alive

Text, photos, videos and files, voice notes with waveforms, emoji, reactions, swipe to reply, read ticks, pinned messages, 24-hour stories, and Bubu the mascot who reacts to your group.

Draw together

A real drawing tool, a shared live canvas everyone paints on at once, timelapse replays, “draw over” any picture, and stickers made from drawings.

Mini-games

Tic-tac-toe, Connect 4, Rock-paper-scissors, Hangman, “Who said that?” from your old messages, and a live Pictionary.

Polls, places, recaps

Polls, location sharing without any map service, birthday confetti, a gallery with “one year ago today”, and a monthly recap with medals.

Voice superpowers

Playback speed, funny voice filters, walkie-talkie mode, and optional transcription by a Whisper container running on your machine.

Simple accounts

A username and a password, the same on every device. Add a contact to a group in one tap, or anyone by their username; invitation codes and QR codes too. Sharing from other apps, in French or English.

Your look

Seven themes (Pastel, Starry night, Notebook, Neo-pop, Plain, Arcade, Forest) with their own fonts and shapes, colours of your choice, light or dark, and a background for each group: patterns or a photo.

Notifications without Google

Android keeps its own connection (no Firebase) and shows conversations like the other messengers: pop-up at the top of the screen and bubbles to answer anywhere. Browsers and the iPhone PWA get encrypted Web Push.

A careful admin

Admin with 2FA, IP allowlist, journal, automatic backups with one-click restore, 30-day trash for groups, open or invitation-only sign-up with links and QR codes, temporary passwords, new version check — and no access to group content.

Take a look

Real screenshots of the web app with made-up friends. The Android app looks the same.

On a computer: groups on the left, the conversation fills the rest.
On a computer: groups on the left, the conversation fills the rest.
The admin: open or invitation-only sign-up, sign-up links and QR codes, health, backups.
The admin: open or invitation-only sign-up, sign-up links and QR codes, health, backups.

Install in two minutes

All you need is a machine with Docker. A reverse proxy with HTTPS is recommended (needed for the installable web app and notifications).

  1. Create docker-compose.yml
    services:
      papote:
        image: ghcr.io/mrddream/papote:latest
        container_name: papote
        restart: unless-stopped
        ports:
          - "${BIND_ADDRESS:-127.0.0.1}:${PORT:-3000}:3000"
        env_file: .env                   # every setting comes from .env
        environment:
          PORT: 3000                     # inside the container
          BACKUP_DIR: /backups
          PAPOTE_PROFILES: ${COMPOSE_PROFILES:-}
        volumes:
          - ./data:/data
          - ${BACKUP_HOST_DIR:-./backups}:/backups
    
      # Optional: voice note transcription (COMPOSE_PROFILES=whisper)
      whisper:
        image: onerahmet/openai-whisper-asr-webservice:v1.9.1
        container_name: papote-whisper
        profiles: ["whisper"]
        restart: unless-stopped
        environment:
          ASR_MODEL: ${WHISPER_MODEL:-small}
          ASR_ENGINE: faster_whisper
        volumes:
          - ./whisper-cache:/root/.cache
  2. And a .env next to it
    ADMIN_PASSWORD=a-long-password-of-your-own
    PORT=3000                  # 3080… if 3000 is taken
    BIND_ADDRESS=127.0.0.1     # 0.0.0.0 if the proxy is elsewhere
    TRUST_PROXY=loopback, uniquelocal
    SECURE_COOKIES=true
    TZ=Europe/Paris
    GIPHY_API_KEY=             # optional: GIFs
    COMPOSE_PROFILES=whisper   # optional: transcription
    MAX_UPLOAD_MB=20
    UPDATE_CHECK=true

    Every variable is explained in the README. Empty COMPOSE_PROFILES = no Whisper.

  3. Start it
    docker compose up -d
  4. Create your account

    Open http://your-server:3000/web/ or the Android app, enter the server address, then a display name, a username and a password. Sign-up is open by default; in /admin you can make it invitation-only and share sign-up links or QR codes.

The apps

Android

Download the APK from the latest release and install it. Android 8 or newer. No Google services needed. New versions install over it, and the app tells you when one is out.

iPhone, iPad, computer

Open https://your-server/web/ in Safari, Chrome, Firefox or Edge. Add it to the home screen to install it and get notifications.

Optional extras

GIFs through GIPHY (relayed by your server) and voice transcription with Whisper: one line each in .env (GIPHY_API_KEY, COMPOSE_PROFILES=whisper).

HTTPS with a reverse proxy

Browsers need HTTPS to install the web app and show notifications. Put Caddy or nginx in front of Papote: it must keep the Host header, pass WebSockets on /ws, accept uploads a bit larger than MAX_UPLOAD_MB, and connect from an address listed in TRUST_PROXY.

Caddy

The certificate, WebSockets and headers are handled on their own. /etc/caddy/Caddyfile, then sudo systemctl reload caddy:

papote.example.com {
    # Optional: the admin only from your local network
    @admin_outside {
        path /admin /admin/*
        not remote_ip 192.168.0.0/16 10.0.0.0/8
    }
    respond @admin_outside 404

    reverse_proxy 127.0.0.1:3000
}

Caddy in Docker next to Papote, in the same compose file: reverse_proxy papote:3000.

nginx

/etc/nginx/sites-available/papote, with a certificate from certbot (sudo certbot certonly --nginx -d papote.example.com):

# WebSocket (/ws): upgrade only when the browser asks for it
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    listen [::]:80;
    server_name papote.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;                         # nginx < 1.25.1: "listen 443 ssl http2;" instead
    server_name papote.example.com;

    ssl_certificate     /etc/letsencrypt/live/papote.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/papote.example.com/privkey.pem;

    client_max_body_size 25m;         # a bit more than MAX_UPLOAD_MB (20)

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_set_header Host $host;      # sign-up links, QR codes, WebSocket origin check
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_read_timeout 1h;            # the WebSocket stays open

    # Optional: the admin only from your local network
    location /admin {
        allow 192.168.0.0/16;
        allow 10.0.0.0/8;
        deny all;
        proxy_pass http://127.0.0.1:3000;
    }

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

Then link it into sites-enabled and run sudo nginx -t && sudo systemctl reload nginx.

To check: in /admin, the sign-up link must start with https://papote.example.com/. If it shows http:// or 127.0.0.1, the proxy isn’t passing Host or X-Forwarded-Proto, or its address isn’t in TRUST_PROXY. Nginx Proxy Manager, Docker and more in the README.

Your data stays home

One SQLite file and a media folder on your disk. Back it up, restore it, take it elsewhere.

No tracking

No analytics, no ads, no third-party scripts. GIPHY never sees your members, maps open only when you tap.

Admin can’t snoop

The admin page shows counts and names, never messages, photos or drawings.