---
# System prepended metadata

title: outline 部署

---

# outline 部署

## 什麼是 Outline


---

<https://www.getoutline.com/>

想找一個能夠替代 Confluence 管理文檔 ，既開源可本地部署，layout 又不會過於年代感的 wiki，找到了這個與 notion  87 分像的開源項目，於是著手開始進行部署設定。


![](https://i.imgur.com/uQEL1in.png)



\
## 環境建立

本篇將使用 docker 完成部署，參考 outline 的官方文件有直接建議 hosting outline 的做法

[Docker](https://app.getoutline.com/share/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t)

### 資料設定


1. Install [Docker Compose](https://docs.docker.com/compose/install/)
2. 建立 `docker-compose.yml` 檔案，並參考以下設定：

   ```yaml
   version: "3"
   services:
   
     outline:
       image: outlinewiki/outline
       env_file: ./docker.env
       ports:
         - "3000:3000"
       depends_on:
         - postgres
         - redis
         - storage
   
     redis:
       image: redis
       env_file: ./docker.env
       ports:
         - "6379:6379"
       volumes:
         - ./redis.conf:/redis.conf
       command: ["redis-server", "/redis.conf"]
       healthcheck:
         test: ["CMD", "redis-cli", "ping"]
         interval: 10s
         timeout: 30s
         retries: 3
   
     postgres:
       image: postgres:14-alpine
       env_file: ./docker.env
       ports:
         - "5432:5432"
       volumes:
         - database-data:/var/lib/postgresql/data
       healthcheck:
         test: ["CMD", "pg_isready -U user"]
         interval: 30s
         timeout: 20s
         retries: 3
       environment:
         POSTGRES_USER: user
         POSTGRES_PASSWORD: pass
         POSTGRES_DB: outline
   
     storage:
       image: minio/minio
       env_file: ./docker.env
       ports:
         - "9000:9000"
         - "9001:9001"
       entrypoint: sh
       volumes:
         - https-portal-data:/var/lib/https-portal
       healthcheck:
         test: ["CMD", "service", "nginx", "status"]
         interval: 30s
         timeout: 20s
         retries: 3
       command: -c 'minio server /data --console-address ":9001"'
       deploy:
         restart_policy:
           condition: on-failure
       volumes:
         - storage-data:/data
       healthcheck:
         test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
         interval: 30s
         timeout: 20s
         retries: 3
   
     https-portal:
       image: steveltn/https-portal
       env_file: ./docker.env
       ports:
         - '80:80'
         - '443:443'
       links:
         - outline
         - storage
       restart: always
       volumes:
         - https-portal-data:/var/lib/https-portal
       healthcheck:
         test: ["CMD", "service", "nginx", "status"]
         interval: 30s
         timeout: 20s
         retries: 3
   
   volumes:
     https-portal-data:
     storage-data:
     database-data:
   ```


與官方建議不同，需要注意的地方：

* 建議將 storage（ minio ）的兩個 port 輸出出來，`:9000` 指向 api，`:9001` 指向 console，承如 minIO 官方建議那樣，稍後也比較好設定其他配置。


3. 建立環境參數 `docker.env`

   ```bash
   # –––––––––––––––– REQUIRED ––––––––––––––––
   
   # Generate a hex-encoded 32-byte random key. You should use `openssl rand -hex 32`
   # in your terminal to generate a random value.
   SECRET_KEY=generate_a_new_key
   
   # Generate a unique random key. The format is not important but you could still use
   # `openssl rand -hex 32` in your terminal to produce this.
   UTILS_SECRET=generate_a_new_key
   
   # For production point these at your databases, in development the default
   # should work out of the box.
   # DATABASE_URL=postgres://user:pass@postgres:5432/outline
   # DATABASE_URL_TEST=postgres://user:pass@postgres:5432/outline-test
   # DATABASE_CONNECTION_POOL_MIN=
   # DATABASE_CONNECTION_POOL_MAX=
   # Uncomment this to disable SSL for connecting to Postgres
   # PGSSLMODE=disable
   
   # For redis you can either specify an ioredis compatible url like this
   
   REDIS_URL=redis://redis:6379
   
   # or alternatively, if you would like to provide addtional connection options,
   # use a base64 encoded JSON connection option object. Refer to the ioredis documentation
   # for a list of available options.
   # Example: Use Redis Sentinel for high availability
   # {"sentinels":[{"host":"sentinel-0","port":26379},{"host":"sentinel-1","port":26379}],"name":"mymaster"}
   # REDIS_URL=ioredis://eyJzZW50aW5lbHMiOlt7Imhvc3QiOiJzZW50aW5lbC0wIiwicG9ydCI6MjYzNzl9LHsiaG9zdCI6InNlbnRpbmVsLTEiLCJwb3J0IjoyNjM3OX1dLCJuYW1lIjoibXltYXN0ZXIifQ==
   
   # URL should point to the fully qualified, publicly accessible URL. If using a
   # proxy the port in URL and PORT may be different.
   URL=<your-outline-url>
   PORT=3002
   
   
   # See [documentation](docs/SERVICES.md) on running a separate collaborationNGUAGE
   # server, for normal operation this does not need to be set.
   COLLABORATION_URL=
   
   # To support uploading of images for avatars and document attachments an
   # s3-compatible storage must be provided. AWS S3 is recommended for redundency
   # however if you want to keep all file storage local an alternative such as
   # minio (https://github.com/minio/minio) can be used.
   
   ## 此段落設定給 storage - minio
   MINIO_SERVER_URL=<your-minio-api-url>
   MINIO_BROWSER_REDIRECT_URL=<your-minio-admin-url>
   MINIO_ROOT_USER=<your-minio-username>
   MINIO_ROOT_PASSWORD=<your-minio-password>
   ##MinIO 自己可以當 Object Storage，如果要 MinIO 去連 S3 需要加上 MINIO_REGION_NAME
   #MINIO_REGION_NAME=
   
   # A more detailed guide on setting up S3 is available here:
   # => https://wiki.generaloutline.com/share/125de1cc-9ff6-424b-8415-0d58c809a40f
   
   AWS_ACCESS_KEY_ID=${MINIO_ROOT_USER}
   AWS_SECRET_ACCESS_KEY=${MINIO_ROOT_PASSWORD}
   #AWS_REGION=${MINIO_REGION_NAME}
   AWS_S3_UPLOAD_BUCKET_URL=${MINIO_SERVER_URL}
   AWS_S3_UPLOAD_BUCKET_NAME=<your-minio-bucket-name>
   AWS_S3_UPLOAD_MAX_SIZE=26214400
   AWS_S3_FORCE_PATH_STYLE=true
   AWS_S3_ACL=private
   
   # –––––––––––––– AUTHENTICATION ––––––––––––––
   
   
   # Third party signin credentials, at least ONE OF EITHER Google, Slack,
   # or Microsoft is required for a working installation or you'll have no sign-in
   # options.
   
   # To configure Slack auth, you'll need to create an Application at
   # => https://api.slack.com/apps
   #
   # When configuring the Client ID, add a redirect URL under "OAuth & Permissions":
   # https://<URL>/auth/slack.callback
   SLACK_CLIENT_ID=get_a_key_from_slack
   SLACK_CLIENT_SECRET=get_the_secret_of_above_key
   
   
   # To configure Google auth, you'll need to create an OAuth Client ID at
   # => https://console.cloud.google.com/apis/credentials
   #
   # When configuring the Client ID, add an Authorized redirect URI:
   # https://<URL>/auth/google.callback
   GOOGLE_CLIENT_ID=
   GOOGLE_CLIENT_SECRET=
   
   # To configure Microsoft/Azure auth, you'll need to create an OAuth Client. See
   # the guide for details on setting up your Azure App:
   # => https://wiki.generaloutline.com/share/dfa77e56-d4d2-4b51-8ff8-84ea6608faa4
   AZURE_CLIENT_ID=
   AZURE_CLIENT_SECRET=
   AZURE_RESOURCE_APP_ID=
   
   # To configure generic OIDC auth, you'll need some kind of identity provider.
   # See documentation for whichever IdP you use to acquire the following info:
   # Redirect URI is https://<URL>/auth/oidc.callback
   # OIDC_CLIENT_ID=75fe43a9d5b8cdd0f154f7ac8e95d668
   # OIDC_CLIENT_SECRET=1029d3ca78ac322d495fbaa59afd394f
   OIDC_AUTH_URI=
   OIDC_TOKEN_URI=
   OIDC_USERINFO_URI=
   
   # Specify which claims to derive user information from
   # Supports any valid JSON path with the JWT payload
   OIDC_USERNAME_CLAIM=preferred_username
   
   # Display name for OIDC authentication
   OIDC_DISPLAY_NAME=OpenID
   
   # Space separated auth scopes.
   OIDC_SCOPES=openid profile email
   
   
   # –––––––––––––––– OPTIONAL ––––––––––––––––
   
   # Base64 encoded private key and certificate for HTTPS termination. This is only
   # required if you do not use an external reverse proxy. See documentation:
   # https://wiki.generaloutline.com/share/1c922644-40d8-41fe-98f9-df2b67239d45
   SSL_KEY=
   SSL_CERT=
   
   # If using a Cloudfront/Cloudflare distribution or similar it can be set below.
   # This will cause paths to javascript, stylesheets, and images to be updated to
   # the hostname defined in CDN_URL. In your CDN configuration the origin server
   # should be set to the same as URL.
   CDN_URL=
   
   # Auto-redirect to https in production. The default is true but you may set to
   # false if you can be sure that SSL is terminated at an external loadbalancer.
   FORCE_HTTPS=false
   
   # Have the installation check for updates by sending anonymized statistics to
   # the maintainers
   ENABLE_UPDATES=true
   
   # How many processes should be spawned. As a reasonable rule divide your servers
   # available memory by 512 for a rough estimate
   WEB_CONCURRENCY=1
   
   # Override the maxium size of document imports, could be required if you have
   # especially large Word documents with embedded imagery
   MAXIMUM_IMPORT_SIZE=5120000
   
   # You can remove this line if your reverse proxy already logs incoming http
   # requests and this ends up being duplicative
   DEBUG=http
   
   # For a complete Slack integration with search and posting to channels the
   # following configs are also needed, some more details
   # => https://wiki.generaloutline.com/share/be25efd1-b3ef-4450-b8e5-c4a4fc11e02a
   #
   SLACK_VERIFICATION_TOKEN=your_token
   SLACK_APP_ID=A0XXXXXXX
   SLACK_MESSAGE_ACTIONS=true
   
   
   # Optionally enable google analytics to track pageviews in the knowledge base
   GOOGLE_ANALYTICS_ID=
   
   # Optionally enable Sentry (sentry.io) to track errors and performance
   SENTRY_DSN=
   
   # To support sending outgoing transactional emails such as "document updated" or
   # "you've been invited" you'll need to provide authentication for an SMTP server
   SMTP_HOST=
   SMTP_PORT=
   SMTP_USERNAME=
   SMTP_PASSWORD=
   SMTP_FROM_EMAIL=
   SMTP_REPLY_EMAIL=
   SMTP_TLS_CIPHERS=
   SMTP_SECURE=true
   
   # Custom logo that displays on the authentication screen, scaled to height: 60px
   # TEAM_LOGO=https://example.com/images/logo.png
   
   # The default interface language. See translate.getoutline.com for a list of
   # available language codes and their rough percentage translated.
   DEFAULT_LANGUAGE=zh_TW
   ```


### 取得 Oauth 

outline 較麻煩得是需要經過第三方認證，在註解 **AUTHENTICATION**  Block 的部分，設定幾個項目就有幾個進入方式，但進入後，上方的 `.env` 註解的部分也有介紹，以下提供幾個方案

#### Google Oauth

* 在[ https://console.cloud.google.com/apis ]( https://console.cloud.google.com/apis ) 底下建立相關資訊，並取得  `GOOGLE_CLIENT_ID `與 `GOOGLE_CLIENT_SECRET`
* 需要注意的是使用 Google Oauth 被授權的 URL 要帶有 **https** 

![](https://i.imgur.com/FA10dOA.png)




#### Slack OAuth

* 在 **Settings** → **Base Information** 取得 `SLACK_CLIENT_ID` 與 `SLACK_CLIENT_SECRET`
* 與 Google oauth 相同，在設定重新導向一樣要帶有 **https** 

再到 **Features** → **Base Information** 設定 `https://<URL>/auth/slack.callback` 


![](https://i.imgur.com/aRSzKfC.png)


#### Custom OIDC

另外找了兩個可以自架第三方授權的項目，可以到此文章最下方的參考文件中的『其他人部署方法』，參考其他人部署 SSO 的方法。

* [Keycloak](https://www.keycloak.org/)
* [docker-sso-server](https://github.com/soulteary/docker-sso-server)


\
### Database

接下來繼續按照官方的方法繼續把資料庫，資料表建立起來：


```bash
docker-compose run --rm outline yarn sequelize db:create --env=production-ssl-disabled 
```


Migrate the new database to add needed tables, indexes, etc:

```bash
docker-compose run --rm outline yarn sequelize db:migrate --env=production-ssl-disabled
```


### Running

建立好 `docker-compose.yml` 與 `docker.env`  後，在此目錄底下執行

```javascript
docker-compose up -d
```


就可以看到畫面了！但在看到畫面後，又遇到一些問題需要修正，再繼續往下看⋯⋯


## 解決問題

### 解決無法編輯文章的問題

部署完畢成功進到 outline 畫面後，會發現右下角出現斷線的 icon，並從 dev tool 中看到錯誤訊息


![](https://i.imgur.com/wx6NXJ0.png)


原因是因為 WebSocket 服務的 Nginx 配置出現問題，WebSocket 有連接的要求，因為要告訴服務端從 http 協議更換到 ws 協議，request / response header 都必須設置： `Upgrade: webSocket`、`Connection: Upgrade`。


### 為什麼要在 Nginx 配置以上兩項 header？

由於 `Upgrade`、`Connection` 都是 hop-by-hop header，在 domain 使用 nginx 做反向代理時，由於以上兩項 header 不會被帶到代理伺服器上，所以無法成功建立 ws 連線。


> hop-by-hop header：only for a single transport-level connection and must not be retransmitted by proxies or cached <Connection、Keep-Alive、Proxy-Authenticate、Trailer、TE、Transfer-Encoding、Upgrade>）


為了解決這個問題，Nginx 提供了 `proxy_set_header` 的配置，將資訊帶至代理伺服器上。

Nginx 配置以下：

```
location / {
  proxy_http_version 1.1; 
  proxy_set_header Upgrade $http_upgrade;
  proxy_set_header Connection "upgrade";
  add_header Service-Worker-Allowed /;
}
```


## 上傳圖片

反向代理設定 minIO ，一樣也需要進行 Nginx 設定，參考[==官網文件==](https://docs.min.io/docs/setup-nginx-proxy-with-minio.html)配置以下：

```
location / {
  proxy_set_header X-Real-IP $remote_addr;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
  proxy_set_header Host $http_host;

  proxy_connect_timeout 300;
  proxy_http_version 1.1;
  proxy_set_header Connection "";
  chunked_transfer_encoding off;

  proxy_pass http://sit-minio-api;
}
```


### minIO 管理介面

storage（minIO）中的 `:9001` 指向 console，即是 minIO 的管理介面。


1. 使用配置中 `MINIO_ROOT_USER` 、 `MINIO_ROOT_PASSWORD` 內容進行登入之後，能夠看到以下畫面



2. 創建一個新的 bucket
   * 名稱為配置中的 `AWS_S3_UPLOAD_BUCKET_NAME`
   * 再進入 **Manage** → **Access Rules** 寫入以下設定

     
   ![](https://i.imgur.com/n01wO49.png)



   * 完成後回到 **Summary** 把 **Access Policy** 從 private 改為 custom，並寫入以下設定

```
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:GetBucketLocation",
                "s3:ListBucketMultipartUploads"
            ],
            "Resource": [
                "arn:aws:s3:::outline"
            ]
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:DeleteObject",
                "s3:GetObject",
                "s3:ListMultipartUploadParts",
                "s3:PutObject",
                "s3:AbortMultipartUpload"
            ],
            "Resource": [
                "arn:aws:s3:::outline/avatars*",
                "arn:aws:s3:::outline/public*",
                "arn:aws:s3:::outline/uploads*"
            ]
        },
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "*"
                ]
            },
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::outline"
            ],
            "Condition": {
                "StringEquals": {
                    "s3:prefix": [
                        "avatars",
                        "public",
                        "uploads"
                    ]
                }
            }
        }
    ]
}
```

以上設定完成後就可以成功上傳圖片與附加檔案！



:::tip
在 outline 中移除圖片並不會徹底刪除，如需徹底刪除圖片需要進到 minIO 的管理頁面，找到上傳的名稱後進行刪除！
:::


## 參考文件

* outline 官方部署：

  <https://app.getoutline.com/share/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t>
* outline github＆Discussions：<https://github.com/outline/outline>
* minIO 官方文件：<https://docs.min.io/docs/>
* outline 討論串 <https://www.reddit.com/r/selfhosted/comments/hq6m6b/how_to_self_host_outline_wiki/>
* 其他人的部署方法：
  * <https://blog.gurucomputing.com.au/doing-more-with-docker/deploying-outline-wiki/>


  * <https://www.luckzym.com/posts/a239536c/>
  * <https://biteax.com/2022/05/07/Outline/%E3%80%90Outline%E3%80%91%E7%BA%AFDocker%E9%83%A8%E7%BD%B2%E6%8C%87%E5%8D%97/>
  * <https://soulteary.com/2021/09/05/opensource-documentation-wiki-software-outline-part-1.html>


