DockerSelf-Hosting

Pocket ID with Docker: passkey-only OIDC login for your apps (Linux, Windows, Mac)

Before you start

  • 20 min read

Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#

Pocket ID is a free, self-hosted login server for your apps: they send users to Pocket ID, users sign in with a passkey (fingerprint, face, PIN or a hardware key), and Pocket ID sends them back signed in. There are no passwords to reuse, leak or reset.

Pocket ID official logo

Logo: the official Pocket ID project (github.com/pocket-id/pocket-id)

It is an OpenID Connect (OIDC) provider, so any app with an "OIDC", "SSO" or "OAuth" sign-in can use it. The project made it for small teams and homelabs that find Keycloak or authentik too much: one container with a built-in SQLite database. The price is two rules: passkeys only and HTTPS (browsers refuse passkeys on plain HTTP, except on localhost).

WhatValue (official docs and release, read November 2026)
Current versionPocket ID v2.19.2, released 2 November 2026 (latest stable, a patch release)
Image to pinghcr.io/pocket-id/pocket-id:v2.19.2 (checked on the registry: linux amd64 and arm64)
Time neededAbout 20 minutes (our estimate, not measured)
Size and RAMImage about 35 MB compressed (amd64, Docker Hub). RAM and CPU: not documented by the project
PortsTCP 1411 (web and API, kept off the internet; the proxy serves HTTPS on 443)
LicenceBSD 2-Clause (LICENSE at the release tag)
Project statusActive: not archived, not renamed. A v3 with a new interface is announced
Official docspocket-id.org/docs and the repository

At a glance:

  • What it is: an OIDC and OAuth 2.0 provider (OpenID Connect Certified, per the README). Each app is an "OIDC client".
  • Passkeys only: no passwords. A passkey lives in a password manager, phone or hardware key. Add a second one, or you can lock yourself out.
  • HTTPS is required: the passkey is tied to the exact address in APP_URL.
  • Pin the tag: v3 will bring breaking changes. This guide uses v2.19.2.
  • Small: one container, one data folder. OIDC only: SAML or LDAP-only apps need an authentication proxy.

How it fits together#

The app sends the browser to Pocket ID, you prove who you are with a passkey, and Pocket ID sends you back with a one-time code. The app then fetches its tokens from Pocket ID directly, so the browser and the app must both reach its HTTPS address.

Three-lane diagram: you open your app, which sends your browser through Caddy or Traefik on port 443 to Pocket ID on port 1411; Pocket ID checks the passkey, uses the data folder, and redirects back to the app with a one-time code; the app fetches tokens and you are signed in

Drawn for this guide from the official docs. Pocket ID stores only the public half of your passkey.

Before you start#

A real setup is a small Linux server with a domain; for a trial, any computer with Docker works.

SystemWhat you need
LinuxDocker Engine and Compose v2, x86_64 or arm64, curl, openssl
WindowsDocker Desktop, PowerShell, curl.exe and tar (in Windows 10 and 11). Fine for a trial
MacDocker Desktop, Terminal. Fine for a trial

Check Docker:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] docker compose version prints a v2 version
  • [ ] The address is decided: http://localhost:1411 (trial) or https://id.example.com (real)
  • [ ] For HTTPS: a DNS record for that name, and ports 80 and 443 free
  • [ ] A password manager, phone or hardware key for passkeys, and a second one

Tested by DIY Lab Hub: not yet. The commands come from the official docs, the compose file and the release files read on 5 November 2026. We did not run them.

Step 1: Create the data folder#

Pocket ID keeps its database and uploads in one folder. We use ~/docker_volumes/pocket-id, as all DIY Lab Hub guides do.

Linux#

Run in terminalbash
mkdir -p ~/docker_volumes/pocket-id/{data,backups}
cd ~/docker_volumes/pocket-id

Windows (PowerShell)#

Codepowershell
New-Item -ItemType Directory -Force "$HOME\docker_volumes\pocket-id\data","$HOME\docker_volumes\pocket-id\backups" | Out-Null
Set-Location "$HOME\docker_volumes\pocket-id"

Mac (Terminal)#

Same commands as Linux:

Run in terminalbash
mkdir -p ~/docker_volumes/pocket-id/{data,backups}
cd ~/docker_volumes/pocket-id

Check it worked. The folder exists: ls -d ~/docker_volumes/pocket-id/data (Windows: Test-Path "$HOME\docker_volumes\pocket-id\data" prints True).

Tip: Keep the data folder on the machine's own disk. The docs warn against SQLite on a network share (NFS, SMB).

Step 2: Write the settings and the Compose file#

.env holds the address and a secret key; docker-compose.yml runs the container. Choose your route first:

RouteAPP_URLWhat to do
Try it on this computerhttp://localhost:1411Follow Steps 2 to 6 as written
Real addresshttps://id.example.comSet this value in .env, do the HTTPS section after Step 3, then continue at Step 4

ENCRYPTION_KEY is mandatory in v2: it encrypts the token-signing keys. The docs generate one with openssl rand -base64 32. The compose file is the official one with two changes: the image is pinned to v2.19.2, and port 1411 is published on 127.0.0.1 only (the official file uses every interface).

Linux#

Run in terminalbash
cd ~/docker_volumes/pocket-id
cat > .env << EOF
APP_URL=http://localhost:1411
ENCRYPTION_KEY=$(openssl rand -base64 32)
TRUST_PROXY=false
EOF
chmod 600 .env
cat > docker-compose.yml << 'EOF'
services:
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id:v2.19.2
    container_name: pocket-id
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:1411:1411"
    volumes:
      - ./data:/app/data
    healthcheck:
      test: ["CMD", "/app/pocket-id", "healthcheck"]
      interval: 1m30s
      timeout: 5s
      retries: 2
      start_period: 10s
EOF

Windows (PowerShell)#

Windows has no openssl by default, so this uses .NET's random generator (our adaptation; the docs only show openssl). The files are written as plain UTF-8 with Unix line endings so the key has no stray characters.

Codepowershell
Set-Location "$HOME\docker_volumes\pocket-id"
$b = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b)
$key = [Convert]::ToBase64String($b)
[System.IO.File]::WriteAllText("$PWD\.env", "APP_URL=http://localhost:1411`nENCRYPTION_KEY=$key`nTRUST_PROXY=false`n")
$compose = @'
services:
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id:v2.19.2
    container_name: pocket-id
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:1411:1411"
    volumes:
      - ./data:/app/data
    healthcheck:
      test: ["CMD", "/app/pocket-id", "healthcheck"]
      interval: 1m30s
      timeout: 5s
      retries: 2
      start_period: 10s
'@
[System.IO.File]::WriteAllText("$PWD\docker-compose.yml", ($compose -replace "`r`n", "`n"))

Mac (Terminal)#

Same commands as Linux. macOS ships openssl, so openssl rand -base64 32 works as written.

Check it worked. The compose file has the pinned tag (grep image docker-compose.yml; Windows: Select-String image docker-compose.yml) and .env has a key line. Never paste the key into a chat or a screenshot.

Tip: Back up .env the moment you create it, in your password manager. If the key changes or goes missing, Pocket ID cannot read its signing keys and will not start (see Common mistakes).

Step 3: Start Pocket ID#

Linux#

Run in terminalbash
cd ~/docker_volumes/pocket-id
docker compose up -d
docker compose ps
docker compose logs --tail=30 pocket-id

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\pocket-id"
docker compose up -d
docker compose ps
docker compose logs --tail=30 pocket-id

Mac (Terminal)#

Same commands as Linux.

Check it worked. docker compose ps lists pocket-id as running. The health check first runs after the 1m30s interval, so it can say health: starting for about a minute and a half and then healthy. The docs publish no expected log text; read the log for errors. Run the health check yourself, exit code 0 means healthy:

Run in terminalbash
docker compose exec pocket-id /app/pocket-id healthcheck

Step 4: Create the admin account and its first passkey#

Open /setup at your APP_URL: http://localhost:1411/setup for the trial, https://id.example.com/setup for the real address (after the HTTPS section). It works only until the first account exists, so the first visitor becomes the admin.

Linux#

  1. Enter a username, email address and name, then click Sign Up.
  2. Click Add Passkey and save it in your password manager, phone or hardware key.

Windows (PowerShell)#

Same browser steps. Windows Hello, a phone or a hardware key work; a password manager extension must support passkeys (see Troubleshooting).

Mac (Terminal)#

Same browser steps; the passkey can go in iCloud Keychain or a password manager.

Check it worked. You land on the admin pages. Open a private window, go to your APP_URL and sign in with the passkey.

Tip: Tried localhost and now want a real domain? Start clean: docker compose down, delete the trial data folder, set the new APP_URL, repeat Steps 3 and 4. A passkey made for one domain does not work on another.

Step 5: Add a second passkey and learn the recovery route#

Passkey-only has one real danger: losing the only passkey. Do both now.

Linux#

  1. In Pocket ID, open My Account and add a second passkey on another device or manager.
  2. Practise recovery from the server. This prints a link that signs the user in once, valid for one hour:
Run in terminalbash
cd ~/docker_volumes/pocket-id
docker compose exec pocket-id /app/pocket-id one-time-access-token "<username-or-email>"

Anyone with that link can sign in as that user. Discard it unused.

Windows (PowerShell)#

Same two steps:

Codepowershell
Set-Location "$HOME\docker_volumes\pocket-id"
docker compose exec pocket-id /app/pocket-id one-time-access-token "<username-or-email>"

Mac (Terminal)#

Same commands as Linux.

Check it worked. My Account lists two passkeys, and the command printed a /lc/ link. Other users get a login code from the admin pages: Administration > Users, the menu of the user, Login Code.

Tip: Add other users with a login code and ask them to add a second passkey right away.

Step 6: Connect your first app#

Every app that signs in with Pocket ID is an OIDC client. You create it in Pocket ID and copy the client ID, client secret and discovery URL into the app. The docs use Immich as the example:

WhereWhat to do
Pocket ID: Administration > OIDC Clients > Add OIDC ClientEnter a name and the app's callback URL (the app's docs name it; Immich needs https://immich.example.com/auth/login, https://immich.example.com/user-settings and app.immich:///oauth-callback)
After CreateCopy the Client ID, the Client secret and the discovery URL. The secret is shown only once
The client's Access tabPick the groups that may sign in, or All Users. A new client lets nobody in until you do
In the appIssuer or authority: your APP_URL. Discovery: <APP_URL>/.well-known/openid-configuration. Scopes: openid profile email, plus groups if the app assigns roles by group

Linux#

Check that the app's server can reach Pocket ID's discovery document (from the app's host or container):

Run in terminalbash
curl -s https://id.example.com/.well-known/openid-configuration | head -c 400

Windows (PowerShell)#

Run in terminalpowershell
curl.exe -s https://id.example.com/.well-known/openid-configuration

Mac (Terminal)#

Same command as Linux.

Check it worked. The output is JSON whose issuer is your APP_URL, and the app's SSO button takes you to Pocket ID and back.

The Pocket ID docs have client examples for nearly a hundred apps, including Immich, Beszel and Vaultwarden, which we have guides for.

Put it behind HTTPS (with or without Traefik)#

Passkeys work only on HTTPS (or localhost), so a real setup needs a reverse proxy in front of port 1411. Trials on localhost can skip this section. Both routes need a domain with a DNS record pointing to the server, and APP_URL must be the exact address in the browser bar.

Both routes make Pocket ID join an external Docker network called proxy and drop the host port. Create the network once and find its address range for TRUST_PROXY (behind a proxy every request comes from the proxy, so Pocket ID must know which proxy to trust for the real client IP):

Run in terminalbash
docker network create proxy
docker network inspect proxy --format "{{range .IPAM.Config}}{{.Subnet}} {{end}}"

Edit ~/docker_volumes/pocket-id/.env so it reads (use your own domain and the subnet printed above; 172.18.0.0/16 is only the docs' example):

File contenttext
APP_URL=https://id.example.com
ENCRYPTION_KEY=<keep-the-key-you-already-have>
TRUST_PROXY=172.18.0.0/16

Tip: Do not set TRUST_PROXY=true unless Pocket ID can only be reached through your proxy: the docs say it trusts every address.

With Traefik#

For a server that already runs Traefik v3 with the Docker provider, add labels and remove the published port.

The labels follow the official reverse-proxy page, which names the entrypoint websecure and the resolver letsencrypt; use the names from your own Traefik (we use https). Replace docker-compose.yml with:

File contentyaml
services:
  pocket-id:
    image: ghcr.io/pocket-id/pocket-id:v2.19.2
    container_name: pocket-id
    restart: unless-stopped
    env_file: .env
    volumes:
      - ./data:/app/data
    healthcheck:
      test: ["CMD", "/app/pocket-id", "healthcheck"]
      interval: 1m30s
      timeout: 5s
      retries: 2
      start_period: 10s
    networks:
      - proxy
    labels:
      traefik.enable: "true"
      traefik.docker.network: proxy
      traefik.http.routers.pocket-id.rule: "Host(`id.example.com`)"
      traefik.http.routers.pocket-id.entrypoints: https
      traefik.http.routers.pocket-id.tls.certresolver: letsencrypt
      traefik.http.services.pocket-id.loadbalancer.server.port: "1411"

networks:
  proxy:
    external: true

Start it with docker compose up -d in ~/docker_volumes/pocket-id (Windows and Mac: the same command in your project folder).

Check it worked. curl -I https://id.example.com/ returns an HTTP status line over HTTPS and the browser shows a valid certificate.

Without Traefik: Caddy#

Choose this if you have no Traefik. Caddy gets and renews its certificates itself.

Caddy gets Let's Encrypt certificates for a public domain automatically, and tls internal gives a local certificate at home. Use the compose file above without the labels: block and run Caddy on the same proxy network. Caddy's Docker docs warn against mounting a single Caddyfile, so we mount a conf folder:

Run in terminalbash
mkdir -p ~/docker_volumes/caddy/{conf,data,config}
cd ~/docker_volumes/caddy

Windows: New-Item -ItemType Directory -Force "$HOME\docker_volumes\caddy\conf","$HOME\docker_volumes\caddy\data","$HOME\docker_volumes\caddy\config". Save as docker-compose.yml in that folder (caddy:2.11.8 is the latest Caddy release read in November 2026):

File contentyaml
services:
  caddy:
    image: caddy:2.11.8
    container_name: caddy
    restart: unless-stopped
    ports:
      - "80:80/tcp"
      - "443:443/tcp"
      - "443:443/udp"
    volumes:
      - ./conf:/etc/caddy
      - ./data:/data
      - ./config:/config
    networks:
      - proxy

networks:
  proxy:
    external: true

Save this as conf/Caddyfile for a public domain (the official Pocket ID block; pocket-id:1411 works because both containers share proxy):

File contenttext
id.example.com {
    reverse_proxy pocket-id:1411
}

For a home-only name use tls internal. The name must resolve to the server on every device (a local DNS record or a hosts-file line), and we did not test passkeys on this exact name:

File contenttext
id.home.arpa {
    tls internal
    reverse_proxy pocket-id:1411
}

Start both stacks:

Run in terminalbash
cd ~/docker_volumes/caddy && docker compose up -d
cd ~/docker_volumes/pocket-id && docker compose up -d

For tls internal, each device must trust Caddy's root certificate. The Caddy docs give these commands, run in ~/docker_volumes/caddy:

Run in terminalbash
# Linux (Debian/Ubuntu style; copied to /tmp first because the target folder needs root)
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo cp /tmp/root.crt /usr/local/share/ca-certificates/root.crt && sudo update-ca-certificates
# Mac
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/root.crt && sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/root.crt

On Windows, in an administrator PowerShell (adapted from the cmd form in the docs):

Run in terminalpowershell
docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt "$env:TEMP\root.crt"
certutil -addstore -f "ROOT" "$env:TEMP\root.crt"

Firefox uses its own store and needs the file imported by hand.

Check it worked. curl -I https://id.example.com/ returns an HTTP status line over HTTPS (Windows: curl.exe -I). With tls internal, the padlock appears only after the root is trusted on that device. Then go to Step 4 and open https://id.example.com/setup.

Tip: Apps that call Pocket ID from their own server must also trust a private certificate. With tls internal this is the usual reason an app login fails while the browser looks fine.

Passkeys only: what it means for you and your users#

QuestionAnswer from the docs
What is a passkey?A key pair: the private half stays on your device, manager or key; Pocket ID stores the public half. It works only on its own domain
Can users have a password?No. Pocket ID supports passwordless methods, primarily passkeys
A device is lost?Add several passkeys per user. An admin can issue a one-time login code, or you create one on the server (Step 5)
Borrowed laptop or TV?Sign in with another device: scan the QR code with your phone and approve (valid 5 minutes)
Which passkeys?Admins choose under Application Configuration > Passkeys: user verification, synced passkeys, device or external keys
Email codes?Optional, and the docs warn they weaken security: anyone who reads the mail can sign in

Tip: A hardware key without a PIN fails with "Your passkey couldn't verify you" when user verification is Required. Set a FIDO2 PIN or choose Preferred.

Pocket ID or authentik?#

Both give your apps one login. Our authentik guide covers the bigger tool.

Pocket IDauthentik (our guide)
ContainersOne (SQLite built in)Server, worker and PostgreSQL
Login methodPasskeys onlyFlows with passwords, MFA and more
ProtocolsOIDC and OAuth 2.0OIDC, SAML, LDAP, forward auth
Apps without OIDCAuthentication proxy (Tinyauth, caddy-security, OAuth2 Proxy, Traefik forward auth)Built-in proxy outpost
RAMNot documentedDocs ask for 2 cores and 2 GB
LicenceBSD 2-ClauseMIT core

Pick Pocket ID if every app supports OIDC and you want the shortest path. Pick authentik if you need SAML, LDAP or detailed policies.

Security and testing#

ControlHow
Port 1411127.0.0.1 only (Step 2), or no host port behind a proxy. Never forward it on the router
ENCRYPTION_KEYRandom, kept in your password manager. ENCRYPTION_KEY_FILE works too; write it with printf '%s', since a trailing newline becomes part of the key
.env filechmod 600 .env; never commit it or paste it in a chat
Who may sign inA client lets nobody in until you pick groups. Prefer groups to All Users
Callback URLsALLOW_INSECURE_CALLBACK_URLS defaults to true; the docs say set false unless a client needs http://
Open signupOnly on a private instance; prefer signup links with an expiry
Rate limitsBuilt in; leave DISABLE_RATE_LIMITING alone
ContainerOptional: -distroless image, read_only: true, non-root user (see the hardening page)
Outgoing requestsVERSION_CHECK_DISABLED=true, ANALYTICS_DISABLED=true, ICON_LIBRARY_URL=disabled

Quick tests after each change (Windows: curl.exe):

Run in terminalbash
cd ~/docker_volumes/pocket-id
docker compose ps
docker compose logs --tail=100 pocket-id
docker compose exec pocket-id /app/pocket-id healthcheck
curl -I https://id.example.com/

Common mistakes#

These come from the docs' own warnings, the GitHub issue tracker (read November 2026) and common AI assistant answers.

MistakeWhy it happensDo this instead
Opening Pocket ID over plain HTTP or on another address than APP_URLPasskeys need HTTPS (or localhost) and are bound to the domain. Issue #1767: "Passkeys are not configured correctly for this domain"; one user had not set APP_URLServe HTTPS and set APP_URL to the exact address in the browser bar
Losing or changing ENCRYPTION_KEYIt protects the signing keys. Issue #1863 (open): failed to decrypt key after an update; the maintainer says the variable changed or is missingKeep the key in your password manager; docker compose exec pocket-id env shows what the container has
Using latest or an unpinned tagv3 will change behaviour; the v2.19.2 notes say to pin a major versionPin v2.19.2 (or v2) and read the changelog before upgrading
One passkey per adminThere is no "forgot password", and /setup disappears after the first accountAdd a second passkey (Step 5) and know the one-time-access-token command
App cannot reach APP_URLThe app's server fetches tokens itself; split DNS or a missing hairpin NAT breaks itMake the name resolve and the certificate trusted inside the app's container, or set INTERNAL_APP_URL
redirect_uri mismatchAn app behind a proxy thinks it runs on http:// and sends that callbackAdd the exact URL from the error; make the app use its public https:// address
New client nobody can useNew clients start with "Selected user groups" and none tickedPick groups on the client's Access tab
SQLite on NFS or SMBThe docs call it dangerous; issues #1274 and #1598 report lock-failure crashesKeep ./data on a local disk, or use PostgreSQL via DB_CONNECTION_STRING
Proxy IP in the audit logBehind a proxy every request comes from it (issue #380)Set TRUST_PROXY to the proxy's address or subnet
AI assistants: inventing a default admin passwordOther tools have one; Pocket ID has noneCreate the first account at /setup
AI assistants: writing v1 settings (DB_PROVIDER, KEYS_STORAGE) or omitting ENCRYPTION_KEYOld examples; v2 removed those and made the key mandatoryUse only the variables on the official environment variables page
AI assistants: promising SAML or LDAP loginPocket ID is OIDC only; LDAP is a read-only sync into itPut such apps behind an authentication proxy

Tip: After every change run docker compose logs --tail=100 pocket-id. A bad key or APP_URL prints there.

Tip: Test the login in a private browser window before telling anyone it works; a normal window may reuse an old session.

Tip: Search the exact error text on the docs' Common issues page first: callback mismatch, "not allowed", Content Security Policy errors and nginx 502 are covered.

Troubleshooting#

ProblemSolution
Browser refuses to create a passkeyThe page must be HTTPS (or localhost) and APP_URL must equal the address bar
/setup shows nothingIt works only until the first account exists. Use the login-code command from Step 5
Container exits at startdocker compose logs --tail=100 pocket-id; look for the key error below
Locked out of the admindocker compose exec pocket-id /app/pocket-id one-time-access-token "<username-or-email>", then open the link within an hour
The redirect_uri is not registered for this clientAdd the URL shown in the error to the client's callback URLs
You are not allowed to access this serviceAdd the user's group on the client's Access tab, or allow all users
nginx 502, upstream sent too big headerRaise proxy_buffer_size, proxy_buffers and proxy_busy_buffers_size as in the official nginx example
Audit log shows the proxy's IPSet TRUST_PROXY to the proxy's address or subnet
Downgrade refusedPocket ID will not start an older version on a newer database. Set ALLOW_DOWNGRADE=true once, then remove it

Key error from issue #1863: failed to decrypt key: failed to decrypt data. The maintainer's answer: ENCRYPTION_KEY changed or is missing in the container.

Known bugs (open, November 2026)#

  • Issue #1740: a Bitwarden-managed passkey cannot be registered in Brave, though a demo site works. Test your password manager, or use a phone or hardware key.
  • Issue #1880: Outlook link scanning can use up the one-time email login link. Give such users an admin-created login code.

Updates and backups#

Update. Pocket ID migrates its database by itself when a new version starts. Within 2.x no setup changes are needed. Read the changelog, back up first, change the tag in docker-compose.yml and pull:

Run in terminalbash
cd ~/docker_volumes/pocket-id
docker compose pull
docker compose up -d
docker compose logs --tail=100 pocket-id

A major release (v3) comes with its own migration guide; the pinned tag keeps you from moving by accident.

Back up. Stop the container so the SQLite file is quiet, archive the data folder and .env, and start again. The archive holds your encryption key, so treat it as secret.

Linux#

Run in terminalbash
cd ~/docker_volumes/pocket-id
docker compose down
tar -czf "backups/pocket-id-$(date +%F).tar.gz" data .env
chmod 600 backups/pocket-id-*.tar.gz
docker compose up -d

Nightly at 03:00: save those lines as backup.sh in the folder, run chmod +x backup.sh, then crontab -e and add:

File contenttext
0 3 * * * /home/<your-user>/docker_volumes/pocket-id/backup.sh

Keep a copy of the archive on another machine.

Windows (PowerShell)#

Codepowershell
Set-Location "$HOME\docker_volumes\pocket-id"
docker compose down
$d = Get-Date -Format "yyyy-MM-dd"
tar -czf "backups\pocket-id-$d.tar.gz" data .env
docker compose up -d

For a nightly run, save these lines as backup.ps1 and register a task:

Run in terminalpowershell
schtasks /Create /SC DAILY /ST 03:00 /TN "PocketID backup" /TR "powershell -NoProfile -File $HOME\docker_volumes\pocket-id\backup.ps1"

Mac (Terminal)#

Same commands as Linux. For the nightly run use cron or launchd.

The docs also describe an experimental pocket-id export for moving instances; we rely on the folder archive.

Restore test. Unpack one archive into a new empty folder, never over the live one:

Run in terminalbash
T=$(mktemp -d)
tar -xzf backups/pocket-id-<date>.tar.gz -C "$T"
ls -la "$T" "$T/data"
rm -rf "$T"

Windows: unpack into a new folder with tar -xzf backups\pocket-id-<date>.tar.gz -C <new-empty-folder>, look, then delete the folder. To restore for real: docker compose down, replace data and .env from the archive, then docker compose up -d.

Performance and resources#

The docs publish no RAM, CPU or disk figures, so this guide gives none. Measure your own:

  • Live use: docker stats pocket-id shows CPU and memory.
  • Image: about 35 MB compressed (amd64, Docker Hub).
  • Ports: 1411 for web and API; an internal task runtime uses UDP 1414 (ACTORS_PORT).
  • Database: SQLite is fine for most installs. PostgreSQL is supported; only one instance may use a database. Issue #1598 links lock failures to a slow or busy host; fixes shipped in 2.14.
  • Logs: LOG_LEVEL, LOG_JSON=true; keep LOG_QUERY_ARGS off.

FAQ#

Is Pocket ID free to use? Yes, under the BSD 2-Clause licence.

Can users have a password as a fallback? No. Only passwordless methods: passkeys, plus admin login codes and optional email codes for first sign-in and recovery.

Do my apps have to support passkeys? No. They only need OIDC; Pocket ID handles the passkey.

Does it send data to the project? Three kinds of outgoing requests: a GitHub version check, a daily anonymous heartbeat and an icon search on jsDelivr. Each has a switch.

Will v3 break my setup? A major release can change settings and will have a migration guide. A pinned 2.x tag keeps you safe until you choose to move.

Sources#

Checked against the official docs on 2026-11-05.