Self-hosting Brave Sync Server v2

published on in category , Tags: brave selfhosted docker

I recently switched to Brave after getting sick of trying to configure media playback to work in a reasonable way on Firefox. I don’t like everything about Brave, but with a bit of tweaking, it makes for a good, relatively Google-free, browser.

There’s little to no documentation on how to self-host the Brave Sync Server, which allows to synchronize bookmarks, tabs, settings and much more between devices.

The sync server, like the browser itself, is open source and can be found here: https://github.com/brave/go-sync.

As always, a word of caution: I don’t take any guarantee or anything, run this thing at your own risk!

Support

  • Desktop: Yes (needs to be started with --sync-server every time)
  • Android: Yes (with a workaround)
  • iOS: No

In general, there’s nothing in the browser itself that explicitly allows you to configure an alternate sync server, except for starting it with --sync-server. There’s an issue to track the UI implementation that made some progress in the last months, so I hope that this will be solved soonish: https://github.com/brave/brave-browser/issues/43181.

Installation

Clone the repo first, there are no pre-built Docker images, so we have to build them ourselves.

git clone https://github.com/brave/go-sync

The included compose file technically works, but is targeted for development, so for an actual day-to-day deployment, we need to make some adjustments. First, adjust docker-compose.yml. We mainly remove the dev container, make data in dynamo-local persistent (Docker container version of Amazon DynamoDB, a NoSQL DB, that’s used for data storage) and remove unecessarily exposed ports.

After those changes, my compose file looks like that:

version: '3.4'

networks:
  sync:
    driver: bridge

services:
  web:
    build:
      context: .
      target: artifact
      args:
        VERSION: "${VERSION}"
        COMMIT: "${COMMIT}"
        BUILD_TIME: "${BUILD_TIME}"
    restart: unless-stopped
    ports:
      - "8295:8295"
    depends_on:
      - dynamo-local
      - redis
    networks:
      - sync
    environment:
      - PPROF_ENABLED=true
      - SENTRY_DSN
      - ENV=local
      - DEBUG=1
      - AWS_ACCESS_KEY_ID=GOSYNC
      - AWS_SECRET_ACCESS_KEY=GOSYNC
      - AWS_REGION=us-west-2
      - AWS_ENDPOINT=http://dynamo-local:8000
      - TABLE_NAME=client-entity-dev
      - REDIS_URL=redis:6379

  dynamo-local:
    build:
      context: .
      dockerfile: dynamo.Dockerfile
    restart: unless-stopped
    volumes:
      - ./dynamo-data:/db
    networks:
      - sync

  redis:
    image: public.ecr.aws/docker/library/redis:6.2
    restart: unless-stopped
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
    networks:
      - sync

In case you’re mounting to a host path like I do, make sure to chown -R root:root the directory. Then start the project:

docker compose up -d --build

Issues running dynamodb-local

The sync server not only builds a custom image for it’s own code, but there’s also a custom image for DynamoDB, which bases on the upstream Amazon image and seeds an initial database setup. The problem is, that the image uses Amazon Linux and apparently requires SSE2 on amd64 (which my VMs don’t have). I also had no success trying to build it on a RPi 4.

To solve this problem, I opted to build my own image on a standard Debian to bypass this restriction.

I replaced the contents of dynamo.Dockerfile with:

FROM openjdk:25-bookworm

# Create working space
WORKDIR /var/dynamodb_wd

# Default port for DynamoDB Local
EXPOSE 8000

# Install DynamoDB
RUN wget -O /tmp/dynamodb_local_latest https://s3-us-west-2.amazonaws.com/dynamodb-local/dynamodb_local_latest.tar.gz && \
    tar xfz /tmp/dynamodb_local_latest

# Install AWS CLI
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" && \
    unzip awscliv2.zip && \
    ./aws/install

# Below is more or less from the original Dockerfile
ENV AWS_ACCESS_KEY_ID=GOSYNC
ENV AWS_SECRET_ACCESS_KEY=GOSYNC
ARG AWS_ENDPOINT=http://localhost:8000
ARG AWS_REGION=us-west-2
ARG TABLE_NAME=client-entity-dev

# Seed Schema
COPY schema/dynamodb/ .
RUN mkdir -p /db && \
    java -jar DynamoDBLocal.jar -sharedDb -dbPath /db & \
    DYNAMO_PID=$! && \
    sleep 15 && \
    aws dynamodb create-table --cli-input-json file://table.json \
    --endpoint-url http://localhost:8000 --region us-west-2 && \
    aws dynamodb update-time-to-live --table-name client-entity-dev \
    --time-to-live-specification "Enabled=true, AttributeName=ExpirationTime" \
    --endpoint-url http://localhost:8000 && \
    kill $DYNAMO_PID


CMD ["java", "-Djava.library.path=.", "-jar", "DynamoDBLocal.jar", "-sharedDb", "-dbPath", "/db", "-port", "8000"]

Building and starting the compose project, and, viola!

Usage

Make sure you’re not connected to the offical Brave sync server before doing that. Syncing to your own server requires an account to be created there which will only work if no sync is set up yet.

Setup on Linux

On my laptop, I installed Brave as a Flatpak app. Since we need to start the app with the aforementioned parameter, I wanted to adjust the menu item on my Gnome desktop to automatically append it. For that, we first need to copy the .desktop file to our local users’ configuration:

cd /var/lib/flatpak/exports/share/applications/
cp com.brave.Browser.desktop ~/.local/share/applications      

Then, edit that file add --sync-url="http://your-brave-host:8295/v2" to each of the Exec= lines. Make sure the order of parameters is correct. --sync-url is supposed to go at the very end.

Log out and log back in again (for Gnome to pick up the override), then launch Brave.

Go to brave://sync-internals first to check if Server URL is pointing to the right URL. Then, go to Sync Settings and set up a new sync chain. Check sync-internals to make sure that synchronization works and no errors appear. Also check the Docker logs of your service. It should not print any access logs, because they are filtered to only show errors.

Setup on Android

On Android, you also need to add the --sync-url parameter. Since you cannot do that normally on a non-rooted device, you need to use adb on your PC to create a file that Brave can read to set the command line args on start.

But first, you need to allow this file being read.

Go to brave://flags and enable “command line on non-rooted devices”. Restart Brave. Then, create a file called /data/local/tmp/chrome-command-line over adb:

adb shell
echo "_ --sync-url=192.168.1.24:8295/v2" > /data/local/tmp/chrome-command-line

Mind the _, we need that! Restart Brave, connect to your sync chain.