Swimming in the Atmospheric River

2026-08-13

In my previous post, Publishing a Zola Blog to AT Protocol with Sequoia and wisp.place, I covered the basics of getting a Zola blog onto AT Protocol. This time, I want to revisit the protocol from a broader perspective and share what I learned while swimming around in the Atmosphere.


AT Protocol and Bluesky

AT Protocol, or atproto, is the foundation of the Bluesky ecosystem. What makes it interesting is that it separates identity, data storage, synchronization, and applications instead of putting everything behind a single service.

At the center of an account is a DID, a portable identifier that remains with the account as it moves between hosting providers. The account's public data lives in a Personal Data Server (PDS) as a signed repository. Relays aggregate repository events into firehoses, while App Views consume that data and turn it into application-specific experiences such as timelines, profiles, search, and other views.

This architecture means that the PDS is not really "the social network." It is closer to the place where your data lives. Bluesky is one application built on top of that data, and other applications can interpret the same repositories through their own lexicons and App Views.

The PDS repository itself is especially interesting. Each account has a self-authenticating repository containing records such as posts, profiles, likes, and follows. The repository is organized as a Merkle Search Tree, and changes produce signed commits that can be independently verified. Repositories can also be exported as CAR files for backup and migration.

That is a rather different mental model from a conventional social media database. Instead of an application owning a database and exposing selected pieces of it, the user's repository is the underlying source of truth, while applications build their own views over it.

This separation is what makes the rest of my experiments possible.


The App Ecosystem

Once I started looking beyond Bluesky itself, I realized how enormous the Atmosphere already is. Atmosphere Account describes itself as "the last social account you'll ever need," and that is not such an unreasonable description after spending some time exploring it.

There are a lot of interesting projects, and unfortunately quite a few of the really polished ones are iOS-only. Some of the ones I found particularly interesting:

What impressed me most was not any individual application. It was the possibility that these could all become different interfaces to the same underlying identity and data.


Migrating My Publishing Workflow

Using these applications feels surprisingly effortless. With all these developer tools available, I started refactoring my otherwise-idling Zola blog to work better with the Atmosphere.

One experiment led to another. I made the site Sequoia-friendly, started publishing Standard.site lexicons, integrated Juttu comments, and eventually set up a CI/CD pipeline in Tangled's Spindle system to build and deploy everything on every commit.

It ended up feeling remarkably close to the GitHub workflow I was already familiar with, except that the pieces were living inside the AT Protocol ecosystem.

The result is the site you are looking at right now: a static Zola blog with Bluesky-powered comments, Standard.site publishing, and an AT Protocol-based build and deployment workflow.

The best part was how little glue code I needed to write myself. The documentation for these projects is generally excellent, and the tools fit together in ways that make experimentation surprisingly pleasant.

I covered the basic publishing setup in my previous post, so I will leave the implementation details there.


Portability, Selfhosting, and Account Migration

Before this expedition, I had already had Bluesky accounts for a while, initially just for experimenting with Bridgy. Now I wanted to actually use the ecosystem.

As someone who enjoys self-hosting, I was particularly excited to discover that I could run my own PDS and keep my account data there. In principle, the various Atmosphere applications can then become different interfaces for interacting with the same underlying social data.

That is a powerful idea.

If an application disappears, my repository does not disappear with it. If my PDS goes down, I can restore or migrate the repository elsewhere. The protocol is explicitly designed around account portability: repositories can be exported and imported, while the DID remains the identity of the account.

I later discovered that there are multiple PDS implementations, ranging from lightweight community projects to Bluesky's own implementation. At the time, I did not know that, so I went with the reference implementation without much hesitation.

Then came the deployment adventure.

Bluesky's PDS initially looked like a collection of shell scripts, but it turned out to be fairly particular about its Linux environment. The documentation suggests Docker as the easiest route, but finding a forever-free Docker host with reasonable storage, reliability, and network flexibility is another story.

So I put it on my homelab server and exposed it through Cloudflared. Not every service I deploy gets that privilege.

The next problem was configuring the PDS. I settled on one of my subdomains after some back and forth. Then I discovered that my email sender's domain reputation was terrible and PLC verification messages were not reaching me.

Fortunately, Resend still allowed me to inspect rejected messages from its dashboard, which helped me to get the PLC token. I used temporary addresses while testing, then evaluated the free domains I had available and eventually found one with a much better reputation. After switching the sender to it, verification and recovery emails started arriving reliably.

Then came the part I was actually nervous about: migration.

I did not want to experiment with my daily-driver account, so I created a temporary test account on pds.rip, which has a short lifetime, and used it to test the process.

To my surprise, migration was almost anticlimactic. I followed this migration guide, ran the all-in-one command, and it worked. My test account has escaped the inevitable fate of deletion.

I thought I was ready.


Challenges and Cautionary Tales

Then I made a mistake.

I decided that my daily-driver account contained too much experimental junk. I thought I should clean it up before migrating so I could "save some storage space."

That innocent idea caused considerably more trouble than I expected.

While inspecting my repository with PDSls, I noticed records that I had deleted from Bluesky but that were still present in the PDS. I started making XRPC requests to remove things manually.

At first, everything seemed fine.

Then Juttu stopped working on my blog.

New posts could be published and comment records could still be created, but the comment threads would not render correctly. The thread index was missing parent information, so even though the comments existed, Bluesky would not show them as replies to the post. The public APIs also failed to expose the expected relationships.

That was the moment I realized I had probably done something I should not have been doing.

I restored my repository from a backup and hoped everything would recover. The repository itself was not corrupted, but the downstream services had apparently ended up with inconsistent views of what had happened.

I eventually stopped trying to fix it myself and contacted Bluesky support. They took some time to respond, and I did not particularly enjoy waiting while my experiment was broken, so I took another route: I moved my content to a new account on my PDS, where the relays and App Views were still processing events normally.

The superficial migration worked. I could continue experimenting while waiting for the old account's indices to reconcile.

And, surprisingly, they eventually did.

By the time Bluesky support replied, without really addressing the questions I had asked, the comments on the old account had started working again. I still do not know whether they changed something on their side or whether the downstream services simply caught up eventually.

Either way, I learned an important lesson.

AT Protocol repositories may look a little like Git repositories, but they are not something you should casually rewrite behind the back of the ecosystem. Repository changes produce signed commits, and those changes propagate through PDSes, relays, and App Views. Downstream services are eventually consistent, and an App View can temporarily have an incomplete view of a repository.

Deleting records is a legitimate protocol operation, but manually manipulating repository state without understanding the synchronization machinery can have consequences that are not immediately visible. The relay firehose carries repository changes onward, and downstream services independently consume and index those events.

So my cautionary advice is simple: do not treat your PDS repository as a database you can freely clean up with arbitrary API calls just because you can see the records.

If you want to experiment at this level, understand the repository and synchronization model first, keep backups, and preferably experiment with an account you can afford to break.

I went into the Atmosphere looking for a nice publishing ecosystem.

I came out with a much better understanding of why the machinery underneath it matters.