Installing bookwyrm using docker and ubuntu

Why?

Because we want to use a federated book review system :)

What do I need to install?

You need a Linux Server with Docker, and Docker-compose installed. creating directories

What's my setup?

Where I can find out more about the project?

How I can install it?

Let's start with it

creating directories

mkdir -p /opt/bookwyrm
mkdir -p /opt/bookwyrm/nginx/conf
mkdir -p /opt/bookwyrm/pgsql/data
mkdir -p /opt/bookwyrm/pgsql/backup
mkdir -p /opt/bookwyrm/data/app/static
mkdir -p /opt/bookwyrm/data/app/media
mkdir -p /opt/bookwyrm/data/redis/config
mkdir -p /opt/bookwyrm/data/redis/activity_data
mkdir -p /opt/bookwyrm/data/redis/broker_data

cloning the project

cd /opt/bookwyrm
git clone https://github.com/bookwyrm-social/bookwyrm.git source
cd source
git checkout production

creating the redis config

copying redis.conf

cd /opt/bookwyrm/source
cp redis.conf /opt/bookwyrm/data/redis/config

creating nginx.conf

creting production.conf

cd /opt/bookwyrm/data/nginx/conf
vim production.conf

content

include /etc/nginx/conf.d/server_config;

upstream web {
    server web:8000;
}

server {
    access_log /var/log/nginx/access.log cache_log;

    listen 80;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    #include /etc/nginx/mime.types;
    #default_type application/octet-stream;

    gzip on;
    gzip_disable "msie6";

    proxy_read_timeout 1800s;
    chunked_transfer_encoding on;

    # store responses to anonymous users for up to 1 minute
    proxy_cache bookwyrm_cache;
    proxy_cache_valid any 1m;
    add_header X-Cache-Status $upstream_cache_status;

    # ignore the set cookie header when deciding to
    # store a response in the cache
    proxy_ignore_headers Cache-Control Set-Cookie Expires;

    # PUT requests always bypass the cache
    # logged in sessions also do not populate the cache
    # to avoid serving personal data to anonymous users
    proxy_cache_methods GET HEAD;
    proxy_no_cache      $cookie_sessionid;
    proxy_cache_bypass  $cookie_sessionid;

    # tell the web container the address of the outside client
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $host;
    proxy_redirect off;

    # rate limit the login or password reset pages
    location ~ ^/(login[^-/]|password-reset|resend-link|2fa-check) {
        limit_req zone=loginlimit;
        proxy_pass http://web;
    }

    # do not log periodic polling requests from logged in users
    location /api/updates/ {
        access_log off;
        proxy_pass http://web;
    }

    # forward any cache misses or bypass to the web container
    location / {
        proxy_pass http://web;
    }

    # directly serve images and static files from the
    # bookwyrm filesystem using sendfile.
    # make the logs quieter by not reporting these requests
    location ~ ^/(images|static)/ {
        root /app/;
        try_files $uri =404;
        add_header X-Cache-Status STATIC;
        access_log off;
    }

    # monitor the celery queues with flower, no caching enabled
    #location /flower/ {
    #   proxy_pass http://flower:8888;
    #   proxy_cache_bypass 1;
    #}
}

creting server_config file

cd /opt/bookwyrm/data/nginx/conf
vim server_config

content

client_max_body_size 10m;
limit_req_zone $binary_remote_addr zone=loginlimit:10m rate=1r/s;

# include the cache status in the log message
log_format cache_log '$upstream_cache_status - '
    '$remote_addr [$time_local] '
    '"$request" $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent" '
    '$upstream_response_time $request_time';

# Create a cache for responses from the web app
proxy_cache_path
    /var/cache/nginx/bookwyrm_cache
    keys_zone=bookwyrm_cache:20m
    loader_threshold=400
    loader_files=400
    max_size=400m;

# use the accept header as part of the cache key
# since activitypub endpoints have both HTML and JSON
# on the same URI.
proxy_cache_key $scheme$proxy_host$uri$is_args$args$http_accept;

docker env config

creating the env config

cd /opt/bookwyrm/source
cp .env.example .env
vim .env

content

SECRET_KEY="a-very-good-secret-here-25-chars-letter-numbers-symbols"

DEBUG=false

USE_HTTPS=true

DOMAIN=domain.tld
EMAIL=help@domain.tld

LANGUAGE_CODE="en-us"
DEFAULT_LANGUAGE="English"

ALLOWED_HOSTS="localhost,127.0.0.1,[::1],domain.tld"

MEDIA_ROOT=images/

# PostgreSQL

PGPORT=5432
POSTGRES_PASSWORD=a-very-good-password-here
POSTGRES_USER=bookwyrm
POSTGRES_DB=bookwyrm
POSTGRES_HOST=db

# Redis activity stream manager

MAX_STREAM_LENGTH=200
REDIS_ACTIVITY_HOST=redis_activity
REDIS_ACTIVITY_PORT=6379
REDIS_ACTIVITY_PASSWORD=a-very-good-password-here

# Redis as celery broker

REDIS_BROKER_HOST=redis_broker
REDIS_BROKER_PORT=6379
REDIS_BROKER_PASSWORD=a-very-good-password-here

# Monitoring for celery

FLOWER_PORT=8888
FLOWER_USER=admin
FLOWER_PASSWORD=a-very-good-password-here

# Email config

EMAIL_HOST=mail.domain.tld
EMAIL_PORT=587
EMAIL_HOST_USER=user@domain.tld
EMAIL_HOST_PASSWORD=a-very-good-password-here
EMAIL_USE_TLS=true
EMAIL_USE_SSL=false
EMAIL_SENDER_NAME=no-reply
EMAIL_SENDER_DOMAIN=domain.tld

# Query timeouts

SEARCH_TIMEOUT=5
QUERY_TIMEOUT=5

# Thumbnails Generation

ENABLE_THUMBNAIL_GENERATION=true

# S3 configuration

USE_S3=false
AWS_ACCESS_KEY_ID=your-access-key-here
AWS_SECRET_ACCESS_KEY=your-secret-access-key-here
AWS_STORAGE_BUCKET_NAME=your-bucket-name-here
AWS_S3_REGION_NAME=your-bucket-region-here
AWS_S3_CUSTOM_DOMAIN=https://[your-bucket-name].[your-endpoint_url]
AWS_S3_ENDPOINT_URL=https://your-endpoint-url

# Preview image generation can be computing and storage intensive

ENABLE_PREVIEW_IMAGES=true

# Specify RGB tuple or RGB hex strings,

PREVIEW_TEXT_COLOR=#363636
PREVIEW_IMG_WIDTH=1200
PREVIEW_IMG_HEIGHT=630
PREVIEW_DEFAULT_COVER_COLOR=#002549

# Set HTTP_X_FORWARDED_PROTO ONLY to true if you know what you are doing.
# Only use it if your proxy is "swallowing" if the original request was made
# via https. Please refer to the Django-Documentation and assess the risks
# for your instance:
# https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header

HTTP_X_FORWARDED_PROTO=false

# TOTP settings

TWO_FACTOR_LOGIN_VALIDITY_WINDOW=2
TWO_FACTOR_LOGIN_MAX_SECONDS=60

# Additional hosts to allow in the Content-Security-Policy, "self" (should be DOMAIN)
# and AWS_S3_CUSTOM_DOMAIN (if used) are added by default.
# Value should be a comma-separated list of host names.
#CSP_ADDITIONAL_HOSTS=

docker-compose config

creating a new docker-compose file

cd /opt/bookwyrm/source
mv docker-compose.yml docker-compose.yml.original
vim docker-compose.yml

content

version: '3'

services:

  nginx:
    image: nginx:latest
    container_name: bookwyrm_nginx
    restart: unless-stopped
    ports:
      - "8001:80"
    depends_on:
      - web
    networks:
      - main
    volumes:
      - .:/app
      - app_static:/app/static
      - app_media:/app/images
      - nginx_conf:/etc/nginx/conf.d

  db:
    build: postgres-docker
    env_file: .env
    container_name: bookwyrm_pgsql
    entrypoint: /bookwyrm-entrypoint.sh
    command: cron postgres
    volumes:
      - pgdata:/var/lib/postgresql/data
      - pgbackup:/backups
    networks:
      - main

  web:
    build: .
    container_name: bookwyrm_web
    env_file: .env
    command: gunicorn bookwyrm.wsgi:application --bind 0.0.0.0:8000
    volumes:
      - .:/app
      - app_static:/app/static
      - app_media:/app/images
    depends_on:
      - db
      - celery_worker
      - redis_activity
    networks:
      - main
    ports:
      - "8000:8000"

  redis_activity:
    image: redis
    container_name: bookwyrm_redis_activity
    command: redis-server --requirepass ${REDIS_ACTIVITY_PASSWORD} --appendonly yes --port ${REDIS_ACTIVITY_PORT}
    volumes:
      - /opt/bookwyrm/data/redis/config/redis.conf:/etc/redis/redis.conf
      - redis_activity_data:/data
    env_file: .env
    networks:
      - main
    restart: on-failure

  redis_broker:
    container_name: bookwyrm_redis_broker
    image: redis
    command: redis-server --requirepass ${REDIS_BROKER_PASSWORD} --appendonly yes --port ${REDIS_BROKER_PORT}
    volumes:
      - /opt/bookwyrm/data/redis/config/redis.conf:/etc/redis/redis.conf
      - redis_broker_data:/data
    env_file: .env
    networks:
      - main
    restart: on-failure

  celery_worker:
    container_name: bookwyrm_celery_worker
    env_file: .env
    build: .
    networks:
      - main
    command: celery -A celerywyrm worker -l info -Q high_priority,medium_priority,low_priority,imports,broadcast
    volumes:
      - .:/app
      - app_static:/app/static
      - app_media:/app/images
    depends_on:
      - db
      - redis_broker
    restart: on-failure

  celery_beat:
    container_name: bookwyrm_celery_beat
    env_file: .env
    build: .
    networks:
      - main
    command: celery -A celerywyrm beat -l INFO --scheduler django_celery_beat.schedulers:DatabaseScheduler
    volumes:
      - .:/app
      - app_static:/app/static
      - app_media:/app/images
    depends_on:
      - celery_worker
    restart: on-failure

  flower:
    container_name: bookwyrm_flower
    build: .
    command: celery -A celerywyrm flower --basic_auth=${FLOWER_USER}:${FLOWER_PASSWORD} --url_prefix=flower
    env_file: .env
    volumes:
      - .:/app
    networks:
      - main
    depends_on:
      - db
      - redis_broker
    restart: on-failure

  dev-tools:
    container_name: bookwyrm_devtools
    build: dev-tools
    env_file: .env
    volumes:
      - .:/app

volumes:
 nginx_conf:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/nginx/conf
      o: bind
 pgdata:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/pgsql/data
      o: bind
 pgbackup:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/pgsql/backup
      o: bind
 app_static:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/app/static
      o: bind
 app_media:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/app/media
      o: bind
 redis_activity_data:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/redis/activity_data
      o: bind
 redis_broker_data:
    driver_opts:
      type: none
      device: /opt/bookwyrm/data/redis/broker_data
      o: bind
networks:
  main:

initializing bookwyrm

database

cd /opt/bookwyrm/source
./bw-dev migrate

containers

cd /opt/bookwyrm/source
docker-compose up -d

initial setup

cd /opt/bookwyrm/source
./bw-dev setup

expect output

...
...
...
*******************************************
Use this code to create your admin account:
c6c35779-BOLHA-IS-COOL-c026610920d6
*******************************************

reverse proxy config

here we are using an external nginx as our reverse proxy, here follows our config, this is just an example

server {
    listen your_listen_ip_here:80;
    server_name domain.tld;
    location / {
        return 301 https://domain.tld$request_uri;
    }
}

server {
    listen your_listen_ip_here:443 ssl http2;
    server_name domain.tld;

    ssl_certificate /etc/letsencrypt/live/domain.tld/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/domain.tld/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_dhparam /etc/letsencrypt/dh-param.pem;

    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';

    ssl_ecdh_curve prime256v1;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    gzip on;
    gzip_types text/css application/javascript image/svg+xml;
    gzip_vary on;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

location / {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://your_docker_instance_ip_here:8000;
        proxy_set_header Host $host;
    }

    location /images/ {
        proxy_pass http://your_docker_instance_ip_here:8001;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
    }

    location /static/ {
        proxy_pass http://your_docker_instance_ip_here:8001;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $host;
    }

}

restart your nginx

finish the bookwyrm config via UI

now, go to your site and finish the configuration using the admin CODE

That's it, we're done, enjoy your Bookwyrm instance!

enabling object storage (s3)

If you want to enable the Object Storage, edit your env config file

cd /opt/bookwyrm/source
vim .env

Adjust the object storage configuration

USE_S3=true
AWS_ACCESS_KEY_ID=your-access-key-here
AWS_SECRET_ACCESS_KEY=your-secret-access-key-here
AWS_STORAGE_BUCKET_NAME=your-bucket-name-here
AWS_S3_REGION_NAME=your-bucket-region-here
AWS_S3_CUSTOM_DOMAIN=https://[your-bucket-name].[your-endpoint_url]
AWS_S3_ENDPOINT_URL=https://your-endpoint-url

Sync the files

./bw-dev copy_media_to_s3

Recreate all containers

cd /opt/bookwyrm/source
docker-compose up -d

That's it!

:)