Deploying Outline Wiki

Introduction

If I haven’t hammered it in enough times during the course of this guide, documentation is important. Just as important as setting up the infrastructure in the first place. Having the insight to document your infrastructure in advance will save you hours down the track. In the business world, it may save your career.

Part of documentation is having a way to organize it, and in the tech world that is usually a wiki. Gitea actually has a wiki built in! However, it’s entirely markdown based (flat text files), and that usually scares people off.

Instead, we will deploy outline to be our wiki and knowledgebase.

Pre-Requisites

If you have been following the whole guide series, you have everything you need in place. If you are coming in just to learn how to deploy outline, this guide uses the following:

Also optionally:

TL;DR

In this conclusive series of our guide, we will set up our final piece of infrastructure: The Outline Wiki

What is Outline?

Outline is a next generation wiki designed around ease of use. I’ve actually written about outline before: it’s an elegant piece of technology, and rivals big name SaaS knowledgebase companies like notion. In fact, I’m using it to draft these guides! Look!

However it’s hard to set up. Like, notoriously hard to set up. Lots of moving parts, some of which are AWS Based. The good news is that everything we’ve built so far contributes to an easy Outline deployment.

My previous setup guide is a bit outdated, and could use a refresh. Let’s set up outline with our new infrastructure.

Nginx Proxy Manager

For Outline, we actually have to supply three subdomains. One will be used for accessing the wiki, one will be dedicated to storing assets and images (this is fairly normal for a cloud native app: we will emulating an s3 storage server), and the last will be for administrating the s3 server. We will use wiki.<my-domain>.<tld>, wikidata.<my-domain>.<tld> , and wikidata-admin.<my-domain>.<tld> respectively.

  • Let’s set up the three subdomains in our DNS server:

  • And create three reverse proxy entries for these domains. First is for what will be our outline installation:

note that even though we are using port 443, we are redirecting to an http backend. This is so outline doesn’t try to redirect to port 80 later on and we get in a loop.

  • And now for our minio (our aws bucket emulation) installation:

  • and finally for our minio admin portal:

Note that we restrict this portal to local subnets only

Setting up Keycloak

Now we have to set up Outline’s authentication. Specifically, dealing with the fact that outline doesn’t have local authentication. Outline has been built with the assumption that you will pair the wiki with an external authentication provider. Good thing we set up Keycloak earlier!

  • In keycloak, create a new client called outline. Give it the URL to your wiki

  • Set the access type to confidential and save.

Crafting the Docker Compose

Alright, we got our pieces together. Let’s actually get the service stood up.

  • Paste the following configuration into gitea. Hold onto your butts, this is a long config (by docker compose standards):

You can find a more complete .env file with descriptions of the environment variables for outline here

version: "2"
services:
  outline_redis:
    image: redis
    restart: always
    container_name: outline_redis
    networks:
      - outline-internal

  outline_postgres:
    image: postgres:13
    restart: always
    container_name: outline_postgres
    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_USER=outline
      - POSTGRES_DB=outline
    networks:
      - outline-internal
    volumes:
      - /mnt/containers/outline/container-data/db:/var/lib/postgresql/data:Z

  outline_minio:
    image: minio/minio
    restart: always
    container_name: outline_minio
    volumes:
      - /mnt/containers/outline/container-data/data:/data:Z
    environment:
      - MINIO_ROOT_USER=minio
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
      - MINIO_BROWSER_REDIRECT_URL=${MINIO_BROWSER_REDIRECT_URL}
    # ports:
      # - 9000:9000
      # - 9001:9001
    networks:
      - reverseproxy-nw
    command: "server /data --console-address :9001"

  outline:
    image: outlinewiki/outline
    user: root
    restart: always
    container_name: outline
    command: sh -c "yarn sequelize:migrate --env=production-ssl-disabled && yarn start --env=production-ssl-disabled"
    depends_on:
      - outline_postgres
      - outline_redis
      - outline_minio
    environment:
      - PGSSLMODE=disable
      - SECRET_KEY=${SECRET_KEY}
      - UTILS_SECRET=${UTILS_SECRET}
      - DATABASE_URL=postgres://outline:${POSTGRES_PASSWORD}@outline_postgres:5432/outline
      - REDIS_URL=redis://outline_redis:6379
      - URL=${WIKI_URL}
      - PORT=443
      - AWS_ACCESS_KEY_ID=minio
      - AWS_REGION=us-east-1
      - AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
      - AWS_S3_UPLOAD_BUCKET_URL=${WIKIDATA_URL}
      - AWS_S3_UPLOAD_BUCKET_NAME=outline
      - AWS_S3_UPLOAD_MAX_SIZE=26214400
      - AWS_S3_FORCE_PATH_STYLE=true
      - AWS_S3_ACL=private
      - OIDC_CLIENT_ID=outline
      - OIDC_CLIENT_SECRET=${AUTH_CLIENT_SECRET}
      - OIDC_AUTH_URI=${AUTH_URL}/auth/realms/master/protocol/openid-connect/auth
      - OIDC_TOKEN_URI=${AUTH_URL}/auth/realms/master/protocol/openid-connect/token
      - OIDC_USERINFO_URI=${AUTH_URL}/auth/realms/master/protocol/openid-connect/userinfo
      - OIDC_USERNAME_CLAIM=email
      - OIDC_DISPLAY_NAME=Keycloak
      - OIDC_SCOPES=email
    # ports:
    #   443:443
    networks:
      - outline-internal
      - reverseproxy-nw

networks:
  outline-internal:
  reverseproxy-nw:
    external: true

  • Now we need to gather three separate secrets. Back in keycloak, get the secret from the client

    credentials page.

  • In cockpit generate two separate random strings using the following:
openssl rand -hex 32

  • In portainer, point a new outline stack at your git repository

  • and fill out the following for environment variables:
POSTGRES_PASSWORD=<a randomly generated password>
MINIO_ROOT_PASSWORD=<a randomly generated password>
MINIO_BROWSER_REDIRECT_URL=https://wikidata-admin.<your-domain>.<tld>
SECRET_KEY=<the first hex key you generated>
UTILS_SECRET=<the second hex key you generated>
WIKI_URL=https://wiki.<your-domain>.<tld>
WIKIDATA_URL=https://wikidata.<your-domain>.<tld>
AUTH_CLIENT_SECRET=<your keycloak client secret>
AUTH_URL=https://auth.<your-domain>.<tld>

  • Deploy! (and cross your fingers)

Success! (Almost)

If all goes well, you can access outline at wiki.<your-domain>.<tld>!

try to Continue with Keycloak, and (fingers crossed) you can log into your functioning wiki!

Well, almost functioning. If you try to paste an image, outline gets unhappy with you:

Creating the Bucket

  • Navigate to https://wikidata-admin.<your-domain>.<tld> and log in with the user minio, and the MINIO_ROOT_PASSWORD you set.

  • Under buckets, create a bucket named outline and save.

Minio has a bug where your bucket name cannot be part of your domain or subdomain. So, for example, if you had outline.<your-domain>.<tld> as you wiki address, you would have to name your bucket something else (and update the environment variable in the stack deployment).

  • Also set up some policies so we can allow public access to the avatars and public folders respectively:

Success (for real!)

  • Back to outline, let’s try that image pasting once more.

Hooray!

Conclusion

With that deployment, we have accomplished our goal. We have the structure needed for a robust docker environment:

  • A web management platform for our docker host and our docker deployments
  • A source control platform for versioning
  • A reverse proxy for IP whitelisting and SSL termination
  • Robust backups of our container data
  • Single sign on for multiple services
  • A powerful wiki for documentation
  • A (primitive) CD (Continuous Delivery) pipeline with Portainer and Gitea

Outline is probably the most complex docker container you will deploy, and we’ve managed to do so without much fuss: We had everything we needed in place to make it mostly painless.

Now that you have all the infrastructure in place, go nuts! Linuxserver has a laundry list of interesting and useful docker services for home use (I recommend jellyfin) and you can now use docker confident in having a secure and protected environment.