As of 2026-01-17, this works but things change rapidly.
High Level Bullet Points
Used the
@atproto/oauth-client-browserto just get building quickly.Examples I based my experiment on:
The BlueSky Team published a package
@atproto/dev-envfor PDS and other backing services for local development.Issue: The node script is missing a
#!/usr/bin/env node, causing it to fail.Created an Issue (#4484) and PR (#4488) to fix it.
Note: The REPL doesn’t work, but it’s not a big deal.
Workaround: You have to install it and edit files manually to get it to work until the PR (or another fix) addresses it.
Requirements: You must have both Redis and Postgres running and exposed to the service.
Expose both connection strings via environment variables:
DB_POSTGRES_URL&REDIS_HOST.See Podman Compose below for container setup.
OAUTH config & metadata for local atproto stack:
clientMetadatafor app:client_idhas to behttp://localhostwithout the port, butredirect_uriandscopequery parameters must be provided.Use
encodeURIComponentto encode them for the URL.Note: The
http://localhostrequirement seems inconsistent with the loopback requirement.
OAuth client config in TypeScript/JavaScript:
You can’t use
localhost; you must use the loopback address (127.0.0.1), otherwise the validation fails in the Oauth client.For fully local development, you must specify
handleResolver,plcDirectoryUrl, andallowHttp=TRUE. See snippet below.
Test Users for atproto localstack:
Your test user’s handle has to end in
.test.You can use a simple curl request to create it. See below.
Note: The default users didn’t work for me.
Example:
handle=alice.test with pass=alice-pass
OAuth Client Config
/* OAUTH client config */
const client = new BrowserOAuthClient({
clientMetadata: getConfig(import.meta.env.PROD),
// https://www.npmjs.com/package/@atproto/oauth-client-browser
handleResolver: "http://127.0.0.1:2583",
plcDirectoryUrl: "http://127.0.0.1:2582",
allowHttp: true,
});Podman Compose for local atproto dev-env
# Podman Compose configuration for atproto PDS development
# Includes PostgreSQL and Redis services
version: '3.8'
services:
postgres:
image: postgres:latest
container_name: atproto_pds_postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
PGDATA: /var/lib/postgresql/data/pgdata
ports:
- '5433:5432'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U postgres']
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres_data:/var/lib/postgresql
restart: unless-stopped
networks:
- atproto_network
redis:
image: redis:latest
container_name: atproto_pds_redis
ports:
- '6379:6379'
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 10s
timeout: 5s
retries: 5
volumes:
- redis_data:/data
restart: unless-stopped
networks:
- atproto_network
volumes:
postgres_data:
name: atproto_postgres_data
redis_data:
name: atproto_redis_data
networks:
atproto_network:
name: atproto_network
driver: bridgeTest User curl command
curl -X POST http://localhost:2583/xrpc/com.atproto.server.createAccount \
-H "Content-Type: application/json" \
-d '{
"handle": "yolo.test",
"email": "yolo@example.com",
"password": "password123"
}'Why LocalStacks are Important?
Being able to run software locally and debug it is vital for a healthy atproto atmosphere and to stop lock in. Developers have been wrestling this back from cloud providers. We should be vigilant about it with atproto.
Likewise, testing data can pollute the main network. PDS.rip helps with this, but it could ultimately disappear any day.