a step by step process into an appview

or, at least, what i had to do to spin up bsky, from a fresh clone towards a smol appview that you can Do Things With

a screenshot of the bluesky frontend with a test account that does not exist on the clearnet, only on my intranet

you will need


plc #

here's my docker-compose file as a reference

services:
  plc:
    image: bsky-plc:f96c898
    network_mode: "host"
    environment:
      - 'DB_CREDS_JSON={"username": "bsky_plc", "password": "...", "port": "5432", "host": "localhost", "database": "bsky_plc"}'
      - 'DB_MIGRATE_CREDS_JSON={"username": "bsky_plc", "password": "...", "port": "5432", "host": "localhost", "database": "bsky_plc"}''
      - "ENABLE_MIGRATIONS=true"
      - "PORT=32990"
      - "DEBUG_MODE=1"
      - "LOG_ENABLED=true"
      - "LOG_LEVEL=debug"
      - "LOG_DESTINATION=1"

relay #

CREATE DATABASE bgs;
CREATE DATABASE carstore;

CREATE USER bsky_relay WITH PASSWORD 'AAAAAAAAAAAAAA';
GRANT ALL PRIVILEGES ON DATABASE bgs TO bsky_relay;
GRANT ALL PRIVILEGES ON DATABASE carstore TO bsky_relay;

-- these are needed for newer versions of postgres
</span>c bgs postgres
GRANT ALL ON SCHEMA public TO bsky_relay;

</span>c carstore postgres
GRANT ALL ON SCHEMA public TO bsky_relay;

services:
  relay:
    image: bsky-relay:a71f3c4d
    network_mode: "host"
    volumes:
      - "/opt/bsky_relay/data_bigsky:/data_bigsky"
      - "/opt/bsky_relay/data_bigsky_events:/data_bigsky_events"
    environment:
      - "ENVIRONMENT=production"
      - "DATABASE_URL=postgres://bsky_relay:AAAAAAAAAAAAAA@localhost:5432/bgs"
      - "CARSTORE_DATABASE_URL=postgres://bsky_relay:AAAAAAAAAAAAAA@localhost:5432/bgs_carstore"
      - "DATA_DIR=/data_bigsky"
      - "RELAY_PERSISTER_DIR=/data_bigsky_events"
      - "GOLOG_LOG_LEVEL=debug"
      - "RESOLVE_ADDRESS=1.1.1.1:53"
      - "FORCE_DNS_UDP=true"
      - "RELAY_COMPACT_INTERVAL=0"
      - "RELAY_DEFAULT_REPO_LIMIT=500000"
      - "MAX_CARSTORE_CONNECTIONS=12"
      - "MAX_METADB_CONNECTIONS=12"
      - "MAX_FETCH_CONCURRENCY=25"
      - "RELAY_CONCURRENCY_PER_PDS=20"
      - "RELAY_MAX_QUEUE_PER_PDS=200"
      - "RELAY_ADMIN_KEY=7cd50598e8ba49458883a3703d41e"
      - "ATP_PLC_HOST=https://plc.asky.ln4.net"
      - "RELAY_SPIDERING=true"

pds #

FROM ghcr.io/bluesky-social/pds:0.4.67
RUN apk --no-cache add ca-certificates
RUN update-ca-certificates -f
# dirty patching is required as for my setups its either localhost 127.0.0.1
# or some other private ip range (like 100.x for tailscale, 172.x for the docker host machine ip, etc).
# for some reason bluesky does explicit checks and breaks if any hostname resolves to these ips. im sure there's a reason internal to them but it's so. absurdly. annoying. patch all the checks for now
RUN sed -i "s/ip.range() === 'unicast'/true/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/src/util.ts
RUN sed -i "s/ip.range() !== 'unicast'/false/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/src/unicast.ts
RUN sed -i "s/ip.range() === 'unicast'/true/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/dist/util.js
RUN sed -i "s/ip.range() !== 'unicast'/false/" /app/node_modules/.pnpm/@atproto-labs+fetch-node@0.1.3/node_modules/@atproto-labs/fetch-node/dist/unicast.js
services:
  pds:
    image: ghcr.io/bluesky-social/pds:0.4.66
    network_mode: "host"
    volumes:
      - "/opt/pds/data:/opt/pds/data"
    environment:
      - "PDS_DATA_DIRECTORY=/opt/pds/data"
      - "PDS_BLOBSTORE_DISK_LOCATION=/opt/pds/data/blobs"
      - "PDS_HOSTNAME=pds.asky.ln4.net"
      - "PDS_PORT=6877"
      - "PDS_BLOB_UPLOAD_LIMIT=52428800"
      - "PDS_DID_PLC_URL=https://plc.asky.ln4.net"
      - "PDS_BSKY_APP_VIEW_URL=https://appview.asky.ln4.net"
      - "PDS_BSKY_APP_VIEW_DID=did:web:appview.asky.ln4.net"
      - "PDS_REPORT_SERVICE_URL=https://mod.asky.ln4.net"
      - "PDS_REPORT_SERVICE_DID=did:web:mod.asky.ln4.net"
      - "PDS_CRAWLERS=https://relay.asky.ln4.net"
      - "LOG_ENABLED=true"
      - "PDS_JWT_SECRET=[redacted]"
      - "PDS_ADMIN_PASSWORD=[redacted]"
      - "PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=[redacted]"

appview #

appview (dataplane) #

services:
  dataplane:
    image: bsky-appview-dataplane:7b40155f0
    network_mode: "host"
    environment:
      - "ENVIRONMENT=production"
      - "POSTGRES_URL=postgres://bsky_dataplane:fbc4751b9eff4426f4eeea77a24aa2579ec6ad94deca936613a84d9@localhost:5432/bsky_dataplane"
      - "POSTGRES_SCHEMA=public"
      - "POSTGRES_POOL_SIZE=10"
      - "NODE_ENV=development"
      - "LOG_ENABLED=true"
      - "LOG_LEVEL=info"
      - "NODE_OPTIONS=--use-openssl-ca"
      - "DID_PLC_URL=https://plc.asky.ln4.net"
      - "RELAY_WEBSOCKET_URL=wss://relay.asky.ln4.net"

appview (image) #

services:
  image:
    image: bsky-appview-image:7b40155f0
    network_mode: "host"
    environment:
      - "PORT=9036"
      - "NODE_ENV=development"
      - "LOG_ENABLED=true"
      - "LOG_LEVEL=info"
      - "NODE_OPTIONS=--use-openssl-ca"
      - "APPVIEW_URL=https://appview.asky.ln4.net"

appview (appview!) #

build:

docker build -t "bsky-appview-server:7b40155f0" . -f services/bsky/Dockerfile

run:

services:
  image:
    image: bsky-appview-server:7b40155f0
    network_mode: "host"
    environment:
      - "NODE_ENV=development"
      - "LOG_ENABLED=true"
      - "LOG_LEVEL=info"
      - "NODE_OPTIONS=--use-openssl-ca"
      - "BSKY_PORT=2584"
      - "PUBLIC_URL=https://appview.asky.ln4.net"
      - "BSKY_DID_PLC_URL=https://plc.asky.ln4.net"
      - "BSKY_SERVER_DID=did:web:appview.asky.ln4.net"
      - "BSKY_BSYNC_URL=http://localhost:39999"
      - "BSKY_BLOB_RATE_LIMIT_BYPASS_KEY=asdf.com"
      - "BSKY_BLOB_RATE_LIMIT_BYPASS_HOSTNAME=asdf.com"
      - "MOD_SERVICE_DID=did:web:appview.asky.ln4.net"
      - "BSKY_DATAPLANE_URLS=https://dataplane.asky.ln4.net"
      - "BSKY_INDEXED_AT_EPOCH=2024-12-21T00:00:00Z"
      - "BSKY_SERVICE_SIGNING_KEY=cdfeb707e884deb9b8d462911c31efdd029a07c5e39f5793838f13109a88509e"
      - "BSKY_CDN_URL=https://image.asky.ln4.net"
      - "BSKY_VIDEO_PLAYLIST_URL_PATTERN=https://video.asky.ln4.net/watch/%s/%s/playlist.m3u8"
      - "BSKY_VIDEO_THUMBNAIL_URL_PATTERN=https://video.asky.ln4.net/watch/%s/%s/thumbnail.jpg"

social-app #

build:

#!/bin/sh
set -eux
cmt=$(git rev-parse --short HEAD)
docker build -t bsky-social-app:${cmt} \
  --build-arg EXPO_PUBLIC_STAGING_SERVICE=https://staging.asky.ln4.net \
  --build-arg EXPO_PUBLIC_BSKY_SERVICE=https://pds.asky.ln4.net \
  --build-arg EXPO_PUBLIC_PUBLIC_BSKY_SERVICE=https://pds.asky.ln4.net \
  --build-arg EXPO_PUBLIC_DEFAULT_FEED=at://did:plc:cx5biu5a5jp46o4pvnl2ngtx/app.bsky.feed.generator/%s \
  --build-arg EXPO_PUBLIC_DISCOVER_FEED_URI=at://did:plc:cx5biu5a5jp46o4pvnl2ngtx/app.bsky.feed.generator/whats-hot \
  --build-arg EXPO_PUBLIC_BSKY_APP_ACCOUNT_DID=did:plc:cx5biu5a5jp46o4pvnl2ngtx \
  --build-arg EXPO_PUBLIC_BSKY_CHAT_DID_REFERENCE=did:web:chat.asky.ln4.net#bsky_chat \ # check DMs section on this!!!!
  -f Dockerfile .

run:

services:
  image:
    image: bsky-social-app:6570f56d8
    command: ['/usr/bin/bskyweb', 'serve']
    network_mode: "host"
    environment:
      - "ATP_APPVIEW_HOST=https://appview.asky.ln4.net"
      - "HTTP_ADDRESS=:8100"

troubleshooting?? #

here's a quick curl to get invite codes to create your account. you can use your deployed frontend to create them

curl --request POST \
          --user "admin:<admin token>" \
          --header "Content-Type: application/json" \
          --data '{"useCount": 1}' \
          "https://pds.asky.ln4.net/xrpc/com.atproto.server.createInviteCode"

feedgen #

i made extragraph to make scriptable lua feedgens on upstream bluesky, but it also has functionality to provide a Following and a Discover feed on its own as well.

you can directly build it via docker build, then

services:
  extragraph:
    image: extragraph:0.0.17
    network_mode: "host"
    restart: on-failure
    volumes:
      - "/opt/extragraph/data:/data:z"
    environment:
      - "PORT=9032"
      - "FOLLOWING_FEED_ENABLE=1"
      - "FOLLOWING_FEED_NAME=following"
      - "FOLLOWING_FEED_DATABASE_PATH=/data/following_feed_state.db"
      - "DISCOVER_FEED_NAME=whats-hot"
      - "FEED_ACTOR_DID=did:plc:cx5biu5a5jp46o4pvnl2ngtx"
      - "SERVICE_ENDPOINT=https://feedgen.asky.ln4.net"
      - "ATPROTO_PLC_URL=https://plc.asky.ln4.net"
      - "RELAY_WEBSOCKET_ADDRESS=wss://relay.asky.ln4.net"
      - "APPVIEW_URL=https://appview.asky.ln4.net"

make sure you have an account on that did:plc! i created one manually and dubbed it "the asky service account". i followed extragraph's own instructions on using npm publishFeed to create the feedgen on the service account. pdsls can be patched so it works on the local setup but that's left as an exercise to the reader (my pdsls is too old and im too lazy to update my patches atm lol)


from this point onwards, the next services are "goodies". they're effectively optional unless you want them.

DMs #

services:
  sasayaki:
    image: sasayaki:0.0.1
    network_mode: "host"
    restart: on-failure
    volumes:
      - "/opt/sasayaki/data:/data"
    environment:
      - "PORT=43091"
      - "APPVIEW_URL=https://appview.asky.ln4.net"
      - "ATPROTO_PLC_URL=https://plc.asky.ln4.net"
      - "SERVER_URL=chat.asky.ln4.net"
      - "DB_PATH=/data/data.db"

videos #

{
  "iat": 6666666,
  "iss": "did:plc:itsmemario",
  "aud": "did:web:pds.asky.ln4.net",
  "exp": 9999999999999,
  "lxm": "com.atproto.repo.uploadBlob",
  "jti": "something here"
}

ozone #

jetstream #