GoToSocial is a lightweight ActivityPub server for small Fediverse instances. It is especially attractive for single-user or small-group deployments where a full Mastodon stack would be unnecessarily heavy.

This guide focuses on a fresh GoToSocial installation in an existing Traefik v3 and CrowdSec Docker ecosystem. GoToSocial runs as a normal Docker Compose stack on the shared proxy network, Traefik handles public HTTPS, and Phanpy is deployed as a separate static web client.

โ„น๏ธ FRESH INSTALL FIRST

The main path below is a clean installation. If you are replacing an existing Mastodon account, use the optional migration section near the end after GoToSocial itself is already working.

Changelog

DateChange
2026-07-01Initial Version: Fresh GoToSocial install with SQLite, Traefik, Phanpy, and optional Mastodon account-move notes.

1. Prerequisites

You need:

  • A working Traefik v3 and CrowdSec stack.
  • A subdomain for GoToSocial, e.g. gotosocial.your-domain.com.
  • A subdomain for Phanpy, e.g. phanpy.your-domain.com.
  • DNS A/AAAA records pointing both subdomains at your server.
  • Docker and Docker Compose.

This guide assumes the Traefik external Docker network is named proxy.

2. GoToSocial Directory

sudo mkdir -p /opt/containers/gotosocial
cd /opt/containers/gotosocial

sudo mkdir -p data cache
sudo chown -R 1000:1000 data cache

The example below runs the container as UID/GID 1000:1000. If your deployment uses another unprivileged user, adjust ownership and user: accordingly.

3. GoToSocial Docker Compose

Create /opt/containers/gotosocial/docker-compose.yml:

services:
  gotosocial:
    image: docker.io/superseriousbusiness/gotosocial:0.22.0
    container_name: gotosocial
    user: 1000:1000
    restart: unless-stopped
    environment:
      GTS_HOST: gotosocial.your-domain.com
      GTS_DB_TYPE: sqlite
      GTS_DB_ADDRESS: /gotosocial/storage/sqlite.db
      GTS_LETSENCRYPT_ENABLED: "false"
      GTS_TRUSTED_PROXIES: 172.18.0.0/16
      GTS_WAZERO_COMPILATION_CACHE: /gotosocial/.cache
      GTS_MEDIA_REMOTE_CACHE_DURATION: "14 days"
      GTS_STATUSES_CLEANUP_REMOTE_OLDER_THAN: "30 days"
      GIN_MODE: release
      TZ: Europe/Berlin
    volumes:
      - ./data:/gotosocial/storage
      - ./cache:/gotosocial/.cache
    networks:
      - proxy
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.gotosocial.rule=Host(`gotosocial.your-domain.com`)"
      - "traefik.http.routers.gotosocial.entrypoints=websecure"
      - "traefik.http.routers.gotosocial.tls.certresolver=tls_resolver"
      - "traefik.http.routers.gotosocial.middlewares=security-headers@file,crowdsec-bouncer@docker"
      - "traefik.http.services.gotosocial.loadbalancer.server.port=8080"

networks:
  proxy:
    external: true

Configuration notes:

  • GTS_LETSENCRYPT_ENABLED=false: Traefik handles public TLS.
  • GTS_TRUSTED_PROXIES: restrict this to your Docker proxy subnet.
  • GTS_MEDIA_REMOTE_CACHE_DURATION: limits cached remote media growth.
  • GTS_STATUSES_CLEANUP_REMOTE_OLDER_THAN: cleans old remote statuses.
  • SQLite is appropriate for a small personal instance.

Start the service:

sudo docker compose pull
sudo docker compose up -d
sudo docker compose logs -f gotosocial

4. Create the First Account

Create the account from inside the container:

sudo docker exec -it gotosocial \
  /gotosocial/gotosocial admin account create \
  --username yourusername \
  --email you@your-domain.com \
  --password 'REPLACE_WITH_A_STRONG_TEMPORARY_PASSWORD'

Promote it to admin:

sudo docker exec -it gotosocial \
  /gotosocial/gotosocial admin account promote \
  --username yourusername

Log in at:

https://gotosocial.your-domain.com/settings

Change the temporary password immediately.

5. Phanpy Web Client

GoToSocial provides settings pages and API endpoints, but you usually want a full client for daily use. Phanpy works well as a static web client.

Create the directory:

sudo mkdir -p /opt/containers/phanpy/html
cd /opt/containers/phanpy

Download and verify the Phanpy release asset you want to run, then extract it into html/.

๐Ÿ’ก VERIFY RELEASE ASSETS

When using prebuilt static client assets, verify the checksum from the release page before serving them publicly.

Create /opt/containers/phanpy/nginx.conf:

worker_processes 2;
user www-data;

events {
    use epoll;
    worker_connections 128;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;

    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;

    server {
        listen 80;
        server_name _;

        root /usr/share/nginx/html;
        index index.html;

        location ~* \.(?:css|js|mjs|png|jpg|jpeg|gif|svg|webp|avif|ico|woff2?)$ {
            try_files $uri =404;
            access_log off;
            expires 30d;
            add_header Cache-Control "public, immutable";
        }

        location / {
            try_files $uri $uri/ /index.html;
            add_header Cache-Control "no-cache";
        }
    }
}

Create /opt/containers/phanpy/docker-compose.yml:

services:
  phanpy:
    image: nginx:1.27.1
    container_name: phanpy
    restart: unless-stopped
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    labels:
      - "traefik.enable=true"
      - "traefik.docker.network=proxy"
      - "traefik.http.routers.phanpy.rule=Host(`phanpy.your-domain.com`)"
      - "traefik.http.routers.phanpy.entrypoints=websecure"
      - "traefik.http.routers.phanpy.tls.certresolver=tls_resolver"
      - "traefik.http.routers.phanpy.middlewares=security-headers@file,crowdsec-bouncer@docker"
      - "traefik.http.services.phanpy.loadbalancer.server.port=80"
    networks:
      - proxy

networks:
  proxy:
    external: true

Start it:

sudo docker compose up -d

Open:

https://phanpy.your-domain.com/

Choose your GoToSocial instance as the server when logging in.

6. Verification

Check the public profile:

curl -I https://gotosocial.your-domain.com/@yourusername

Check WebFinger:

curl -s 'https://gotosocial.your-domain.com/.well-known/webfinger?resource=acct:yourusername@gotosocial.your-domain.com'

Check logs:

cd /opt/containers/gotosocial
sudo docker compose logs -f gotosocial

Verify the Phanpy route:

curl -I https://phanpy.your-domain.com/

At this point, the fresh GoToSocial installation is ready. You can use Phanpy or any compatible Mastodon/ActivityPub client to log in.

A fresh GoToSocial instance only knows local accounts and remote objects it has already discovered. Global search is not a search engine over the entire Fediverse.

To discover more content:

  • Follow accounts from known instances.
  • Open remote profile URLs directly in your client.
  • Follow hashtags.
  • Interact with posts so your instance learns about those actors and threads.

It is normal for trending posts or broad search results to look empty at first.

8. Backups and Updates

Back up:

/opt/containers/gotosocial/data
/opt/containers/gotosocial/docker-compose.yml
/opt/containers/phanpy/html
/opt/containers/phanpy/docker-compose.yml
/opt/containers/phanpy/nginx.conf

Update GoToSocial by reading the release notes, changing the image tag, pulling, and recreating:

cd /opt/containers/gotosocial
sudo docker compose pull
sudo docker compose up -d --remove-orphans

For Phanpy, replace the static release files in html/ with a verified newer release and recreate the Nginx container if needed.

9. Optional: Migrating from Mastodon

GoToSocial is not a drop-in Mastodon database replacement. If you are replacing an existing Mastodon account, treat it as an ActivityPub identity move after the new GoToSocial instance has been installed and tested.

The migration flow is:

  1. Keep the old Mastodon account reachable.
  2. Create the new GoToSocial account.
  3. Add an alias in GoToSocial pointing to the old Mastodon account.
  4. Trigger the account move from the old Mastodon account to the new GoToSocial account.
  5. Import or recreate follows.
  6. Recreate hashtag follows separately if needed.
โš ๏ธ KEEP THE OLD ACCOUNT REACHABLE

ActivityPub account moves depend on the old account being reachable long enough for other servers to observe the move. Do not shut down the old Mastodon hostname immediately after changing DNS.

9.1. Add the GoToSocial Alias

In GoToSocial settings:

https://gotosocial.your-domain.com/settings/user/migration

Add the old Mastodon account as an alias, for example:

https://mastodon.your-domain.com/@yourusername

or:

https://mastodon.your-domain.com/users/yourusername

9.2. Trigger the Move in Mastodon

Log in to the old Mastodon account and move it to:

@yourusername@gotosocial.your-domain.com

Mastodon will mark the old profile as moved and notify followersโ€™ servers. Propagation is not instant.

9.3. Following and Hashtags

Follower migration is handled by the ActivityPub move. Following relationships are different: export your Mastodon following list and import it into GoToSocial if the UI supports it, or recreate the follows through a client/API workflow.

Hashtag follows are not guaranteed to migrate with account moves or account CSV imports. Export and recreate them separately.

After the account move, search for the old Mastodon account from another Fediverse instance and verify that it points to the new GoToSocial account.

10. Optional: Legacy Mastodon Proxy

During a migration, it can be useful to keep mastodon.your-domain.com reachable even after the new server handles DNS. A temporary Traefik file-provider route can proxy the old Mastodon host back to the old server.

Remove this after the fallback window has ended. Keeping a legacy proxy forever makes the migration harder to reason about.

Conclusion

GoToSocial fits neatly into a Traefik-based Docker setup: one small application container, SQLite storage for small instances, no public host port, and a separate static web client through the same reverse proxy. If you later replace Mastodon, handle that as an ActivityPub account move, not as a database transplant.

๐Ÿ“šGOTOSOCIAL DOCUMENTATION ๐Ÿ“ฆGOTOSOCIAL CODEBERG ๐ŸŒPHANPY