
Outline Wiki with Docker Compose: Team Knowledge Base Guide
Every team eventually ends up with the same mess: the server password lives in a chat thread from last spring, the onboarding checklist exists only in one colleague’s head, and the “final” version of the deployment guide is a document nobody can find anymore. A team wiki fixes that, but the popular hosted ones charge per seat, keep your knowledge on someone else’s servers and get sluggish once you pass a few thousand pages. Outline is the alternative we keep recommending: it feels like Notion, it is genuinely fast, and with Docker Compose you can run it on your own VPS in well under an hour.
In this tutorial we build a complete outline wiki docker compose stack from scratch: Outline itself, PostgreSQL for the data, Redis for the real-time features, local disk storage for uploads, Caddy for automatic HTTPS, and login through either your company’s single sign-on (OIDC) or simple email links. Every step is explained, so you do not need to be a seasoned sysadmin to follow along.
What Outline is and why teams like it
Outline (getoutline.com , source code on github.com/outline/outline ) is a knowledge base application for teams. You organize documents into collections (think “Engineering”, “Sales”, “HR”), nest pages inside pages, and everyone edits in a clean, Markdown-friendly editor where changes from several people appear live, the way they do in Google Docs.
What you get out of the box:
- Real-time collaborative editing, with Markdown shortcuts and a “/” menu for tables, code blocks, checklists and embeds.
- Fast full-text search across every document you have access to.
- Permissions per collection, so the HR folder stays private while the onboarding guide is open to everyone.
- Revision history, comments and templates, plus public share links when you want to show a page to a customer.
- An API and integrations (Slack, embeds for many common tools) and an interface translated into many languages.
Two things surprise first-time installers, so let us say them up front:
- Outline has no built-in username and password login. People sign in through an identity provider (OIDC, Google, Microsoft, Slack and others) or through a “magic link” sent by email. If you start the app without configuring one of these, you will look at a login page with no way in. Step 6 is dedicated to this.
- The license is BSL 1.1 (Business Source License). Self-hosting for your own team is allowed and free; what you may not do is resell Outline as a competing hosted service. Each release converts to an open source license after a few years.
A few terms we will use
- Container: a packaged application with everything it needs to run, isolated from the rest of the server. Outline, PostgreSQL, Redis and Caddy each run in their own container.
- Docker Compose: a tool that starts several containers together from one text file (
docker-compose.yml), so the whole stack is described in a single place. - Volume: a storage area managed by Docker that survives container restarts and upgrades. Your documents and uploads live in volumes, not inside the containers.
- Port: a numbered “door” on the server. Web traffic uses 80 (HTTP) and 443 (HTTPS); Outline listens on 3000 internally.
- Reverse proxy: a front door web server (here Caddy) that receives HTTPS requests from the internet, handles the TLS certificate and forwards them to the right container.
- OIDC (OpenID Connect): a standard way to say “let another system confirm who this user is”. Keycloak, Authentik, Google Workspace and Microsoft Entra ID all speak it.
- SMTP: the protocol used to send email. Outline needs it for magic links, invitations and notifications.
How the Outline stack fits together
The browser only ever talks to Caddy. Caddy terminates HTTPS and passes the request to Outline on the private Docker network. Outline stores documents, users and the search index in PostgreSQL, uses Redis for background jobs and to sync live edits between users, and writes uploaded images and attachments to a Docker volume on the local disk. When someone logs in, Outline talks outbound to your OIDC provider or your SMTP server.
What you need
- A VPS with at least 2 vCPU and 2 GB of RAM (4 GB is comfortable for a team of 20 or more, or if you import large spaces), 20 GB of disk or more, running Debian 13 or Ubuntu 24.04. A KVM VPS is the simplest choice; see the note about LXC below.
- A domain or subdomain you control, for example
wiki.example.com, where you can create a DNS record. - SSH access as a regular user with sudo rights. All commands below are run as that user, not as root.
- One login method: either an OIDC provider (Keycloak, Authentik, Google Workspace, Microsoft Entra ID and so on) where you can register an application, or SMTP credentials for a mailbox that can send email. Ideally both.
- About 45 minutes, including waiting for DNS.
A note on LXC containers: Docker inside an unprivileged LXC container only works if the host enables nesting (and usually keyctl) for that container. If you are on a DreamServer LXC plan, ask support to confirm those features are enabled, or simply choose a KVM VPS, where Docker runs without any special setup.
How much memory will it actually use? For a small team, the whole stack sits well under 1 GB at idle. Outline itself, a Node.js application, is the largest piece by far:
The installation at a glance
Here is the whole procedure. Each box matches one “Step” heading below, in the same order.
Step 1: Prepare the VPS and DNS
Start with an up-to-date system. Log in over SSH and run:
sudo apt update && sudo apt full-upgrade -y
This refreshes the package lists and installs all pending updates. Should you reboot afterwards? On Ubuntu, check whether the file /var/run/reboot-required exists (ls /var/run/reboot-required); if it does, run sudo reboot. Debian does not always create that file, so on Debian simply reboot if the upgrade installed a new linux-image package.
Next, point your domain at the server. In your DNS panel, create an A record for wiki.example.com with your VPS’s IPv4 address (and an AAAA record with the IPv6 address, if your VPS has one). Replace wiki.example.com with your real domain everywhere in this guide. After a few minutes, check from the server:
getent hosts wiki.example.com
You should see your VPS IP address. If nothing comes back, wait a bit longer; Caddy cannot obtain a certificate until DNS resolves correctly.
Now a basic firewall. A firewall decides which ports accept connections. We use ufw, which is preinstalled on Ubuntu 24.04 but must be installed on Debian 13:
sudo apt install -y ufw
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443
sudo ufw enable
sudo ufw status verbose
The status output should list 22, 80 and 443 as ALLOW IN. If your SSH daemon listens on a different port, allow that port instead of 22 before running ufw enable, or you will lock yourself out. One important detail: ports published by Docker bypass ufw entirely, because Docker writes its own firewall rules. The 80/443 rules above are documentation of intent; the real protection for Outline is that we never publish its port 3000 at all.
Step 2: Install Docker Engine and the Compose plugin
We install Docker from Docker’s own repository, which ships newer versions than the distribution packages and includes the docker compose plugin. The repository is described in Docker’s current deb822 format, in the file /etc/apt/sources.list.d/docker.sources:
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
. /etc/os-release
sudo curl -fsSL "https://download.docker.com/linux/${ID}/gpg" -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/${ID}
Suites: ${VERSION_CODENAME}
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
The line . /etc/os-release loads two useful variables: ID (which is debian or ubuntu) and VERSION_CODENAME (trixie on Debian 13, noble on Ubuntu 24.04). That way the same block works on both systems. You can check the result with cat /etc/apt/sources.list.d/docker.sources.
Let your user run Docker without typing sudo every time, then log out and back in so the group change takes effect:
sudo systemctl enable --now docker
sudo usermod -aG docker "$USER"
exit
After reconnecting, verify:
docker run --rm hello-world
docker compose version
You should see the “Hello from Docker!” message and a Compose version starting with v2. Keep in mind that membership in the docker group is effectively root access, so only add trusted accounts.
Step 3: Create the project folder and generate secrets
Everything for this stack lives in one folder, ~/outline. Outline needs two random secrets, and PostgreSQL needs a password. We generate all of them with openssl so you never have to invent one:
mkdir -p ~/outline && cd ~/outline
WIKI_DOMAIN=wiki.example.com
echo "POSTGRES_PASSWORD=$(openssl rand -hex 24)" > .env
cat > outline.env <<EOF
NODE_ENV=production
URL=https://${WIKI_DOMAIN}
PORT=3000
SECRET_KEY=$(openssl rand -hex 32)
UTILS_SECRET=$(openssl rand -hex 32)
PGSSLMODE=disable
REDIS_URL=redis://redis:6379
FILE_STORAGE=local
FILE_STORAGE_LOCAL_ROOT_DIR=/var/lib/outline/data
FILE_STORAGE_UPLOAD_MAX_SIZE=262144000
FORCE_HTTPS=true
DEFAULT_LANGUAGE=en_US
EOF
chmod 600 .env outline.env
cat outline.env
Change wiki.example.com on the second line to your domain before running the block. What these files contain:
.envis read automatically by Docker Compose. It holds onlyPOSTGRES_PASSWORD, which the compose file uses both for the database container and to build Outline’s connection string. Hex characters are URL-safe, which matters because the password ends up inside a URL.outline.envis passed to the Outline container.URLmust be exactly the public address users type, withhttps://and no trailing slash.SECRET_KEYencrypts sensitive data in the database,UTILS_SECRETprotects internal maintenance endpoints.PGSSLMODE=disableis correct because PostgreSQL is reached over the private Docker network.FILE_STORAGE=localtells Outline to store uploads on disk instead of S3, andFILE_STORAGE_UPLOAD_MAX_SIZEis the maximum upload size in bytes (here 250 MB).
The cat outline.env at the end should show two long hex strings for the secrets and your real domain in URL. Back up these two files somewhere safe. If you lose SECRET_KEY, encrypted data in your database cannot be recovered.
Step 4: Write the docker-compose.yml for Outline
Now the heart of the outline wiki docker compose setup. Create the file with nano docker-compose.yml and paste:
name: outline
services:
outline:
image: docker.getoutline.com/outlinewiki/outline:latest
restart: unless-stopped
env_file: ./outline.env
environment:
DATABASE_URL: postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline
volumes:
- files:/var/lib/outline/data
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
image: postgres:17
restart: unless-stopped
environment:
POSTGRES_USER: outline
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: outline
volumes:
- db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U outline -d outline"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:8-alpine
restart: unless-stopped
volumes:
- redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- outline
volumes:
files:
db:
redis:
caddy_data:
caddy_config:
Save with Ctrl+O, Enter, then Ctrl+X. Here is what each part does:
name: outlinefixes the project name, so volumes get predictable names such asoutline_filesandoutline_db. We rely on that in Step 8.outlineuses the official image from Outline’s registry (the same image is mirrored on Docker Hub asoutlinewiki/outline). Notice it has noports:section: it is reachable only by Caddy, inside the Docker network.depends_onwithservice_healthywaits until PostgreSQL and Redis actually answer before Outline starts, which avoids a crash loop on the first boot.- About
:latest: Outline does not publish a “major” tag, solatestis convenient but means everydocker compose pullmay jump to a new release. Once everything works, we recommend replacinglatestwith the exact version number from the releases page (for exampleoutlinewiki/outline:X.Y.Z) and upgrading deliberately. postgres:17follows the current major version and gets security patches within that major automatically. Do not change the major number later without a dump and restore, because PostgreSQL major versions use incompatible data formats. Check the official image page for the newest major when you install.redis:8-alpineis a small Redis image; Outline uses Redis for job queues and to relay live editing between users. The7-alpinetag works too if you prefer it.caddy:2is the only service with published ports: 80 for the certificate challenge and redirects, 443 for HTTPS (TCP and UDP, the latter for HTTP/3).- The
volumes:block declares named volumes. Data in them survivesdocker compose down, upgrades and reboots.
Validate the file without printing your secrets:
docker compose config --quiet
No output means the syntax is correct. An error message will point you to the line with a problem, most often an indentation mistake (YAML requires spaces, never tabs).
Step 5: Put Caddy in front as the reverse proxy
Caddy needs a tiny configuration file, the Caddyfile. It obtains and renews a free Let’s Encrypt certificate on its own, redirects HTTP to HTTPS and forwards everything, including the WebSocket connections Outline uses for live collaboration, to the Outline container:
cd ~/outline
cat > Caddyfile <<EOF
${WIKI_DOMAIN} {
encode zstd gzip
reverse_proxy outline:3000
}
EOF
cat Caddyfile
The output must start with your real domain followed by {. If you opened a new SSH session since Step 3, the WIKI_DOMAIN variable is gone and the file would start with just {; in that case set it again (WIKI_DOMAIN=wiki.example.com) and rerun the block.
encode zstd gzip compresses responses for faster page loads, and reverse_proxy outline:3000 uses the service name from the compose file as a hostname, which Docker resolves on the internal network. If you prefer nginx over Caddy, remember to pass the Upgrade and Connection headers for WebSockets, set X-Forwarded-Proto, and raise client_max_body_size, otherwise live editing and large uploads will fail.
Step 6: Configure login with OIDC or email
As mentioned at the start, Outline has no local passwords, so before the first start you must configure at least one way in. We cover the two most universal ones. You can enable both at the same time, and we recommend configuring SMTP even if you use OIDC, because Outline also sends invitations and notifications by email.
Option A: single sign-on with OIDC
In your identity provider (Keycloak, Authentik, Google Workspace, Microsoft Entra ID and so on), create a new OIDC application (sometimes called a “client”) with these settings:
- Client type: confidential, with a client secret.
- Redirect URI (also called callback URL):
https://wiki.example.com/auth/oidc.callback, exactly, with your domain. - Scopes:
openid profile email. Outline needs the user’s email address, so make sure your users have one in the provider.
Your provider then shows a client ID and a client secret. You also need its endpoint URLs. Every OIDC provider publishes them in a “discovery document” at <issuer>/.well-known/openid-configuration; you can extract them like this (Keycloak-style URL shown):
curl -s https://sso.example.com/realms/team/.well-known/openid-configuration \
| grep -o '"[a-z_]*endpoint" *: *"[^"]*"'
Look for authorization_endpoint, token_endpoint, userinfo_endpoint and end_session_endpoint. For Authentik, the paths look like /application/o/authorize/, /application/o/token/ and /application/o/userinfo/. Now append the settings to outline.env:
cd ~/outline
cat >> outline.env <<'EOF'
OIDC_CLIENT_ID=outline
OIDC_CLIENT_SECRET=paste-the-client-secret-here
OIDC_AUTH_URI=https://sso.example.com/realms/team/protocol/openid-connect/auth
OIDC_TOKEN_URI=https://sso.example.com/realms/team/protocol/openid-connect/token
OIDC_USERINFO_URI=https://sso.example.com/realms/team/protocol/openid-connect/userinfo
OIDC_LOGOUT_URI=https://sso.example.com/realms/team/protocol/openid-connect/logout
OIDC_USERNAME_CLAIM=preferred_username
OIDC_DISPLAY_NAME=Company SSO
OIDC_SCOPES=openid profile email
EOF
nano outline.env
The quotes around 'EOF' stop the shell from interpreting anything inside, so values are written literally. Then, in nano, replace the placeholder secret and the example URLs with your real values. OIDC_DISPLAY_NAME is the text on the login button (“Continue with Company SSO”). OIDC_LOGOUT_URI is optional; when set, logging out of Outline also logs the user out of the provider.
A practical tip: restrict who may use Outline in the identity provider (for example, only members of a “wiki” group get access to the application). That is the cleanest way to control access with OIDC.
Option B: email magic links via SMTP
With SMTP configured, users type their email address on the login page and receive a one-time sign-in link. Use the credentials of a mailbox or transactional email service that is allowed to send from your domain:
cd ~/outline
cat >> outline.env <<'EOF'
SMTP_HOST=smtp.example.com
SMTP_PORT=587
SMTP_USERNAME=wiki@example.com
SMTP_PASSWORD=paste-the-smtp-password-here
SMTP_FROM_EMAIL=wiki@example.com
SMTP_SECURE=false
EOF
nano outline.env
Use port 587 with SMTP_SECURE=false (the connection is upgraded with STARTTLS) or port 465 with SMTP_SECURE=true (TLS from the first byte). Mixing them up is the most common reason emails silently fail. Avoid port 25: many networks restrict outbound traffic on it, and it is meant for server-to-server delivery anyway.
How do the first users get in with email only? In recent Outline releases, a fresh install with SMTP configured and no SSO provider shows an email field that lets the first person create the workspace. Everyone after that joins through an invitation, or because their email domain is on the workspace’s allowed list. If your version does not show the email field on an empty install, configure OIDC (Option A) for the first login, then enable email sign-in for everyone else in the security settings.
Step 7: Start the stack and create your workspace
Time to start everything:
cd ~/outline
docker compose up -d
docker compose ps
The first run downloads the images, which takes a minute or two. docker compose ps should list four services with the status Up, and postgres and redis should also show (healthy). Now follow Outline’s log while it initializes:
docker compose logs -f outline
On the first start, Outline creates its tables in PostgreSQL (database migrations, which it runs automatically), then reports that it is listening on port 3000. The exact wording changes between versions. Press Ctrl+C to stop following the log; the containers keep running. Check that Caddy got its certificate:
curl -I https://wiki.example.com
A response starting with HTTP/2 200 (or a 30x redirect) means HTTPS works end to end. If you see a certificate error instead, look at docker compose logs caddy.
Open https://wiki.example.com in your browser. You should see a login button for your SSO and/or an email field. The first person to sign in creates the workspace and becomes its admin, so do this yourself right away. Then, in Settings:
- Details: set the workspace name, logo and, if you want, a custom subdomain-style name.
- Security: decide who may join. With email login, set the allowed email domains (for example only
example.com) or keep sign-up invite-only, and choose the default role for new members. - Members: invite colleagues by email; they get a link to join.
Create your first collection, write a page, drag in an image, and open the same page in a second browser window to watch live editing in action. If the image appears and edits sync between windows, Redis, local storage and WebSockets are all working.
Whenever you change outline.env later, apply it with docker compose up -d --force-recreate outline, which recreates only the Outline container and leaves your data untouched.
Step 8: Back up Outline and test a restore
A wiki quickly becomes the place where your team’s most important knowledge lives, so backups are not optional. Three things need saving: the PostgreSQL database (documents, users, permissions), the files volume (uploads and images) and your configuration files (especially outline.env with SECRET_KEY). Redis holds only temporary queue data and does not need backing up.
Create ~/outline/backup.sh:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
STAMP=$(date +%F)
mkdir -p backups
chmod 700 backups
docker compose exec -T postgres pg_dump -U outline -d outline -Fc > "backups/outline-db-$STAMP.dump"
docker run --rm -v outline_files:/data:ro -v "$PWD/backups":/backup alpine \
sh -c "tar czf /backup/outline-files-$STAMP.tar.gz -C /data . && chown $(id -u):$(id -g) /backup/outline-files-$STAMP.tar.gz"
tar czf "backups/outline-config-$STAMP.tar.gz" .env outline.env docker-compose.yml Caddyfile
find backups -type f -mtime +14 -delete
echo "Backup $STAMP done"
Make it executable and run it once:
chmod +x ~/outline/backup.sh
~/outline/backup.sh
ls -lh ~/outline/backups
You should see three files with today’s date. Some notes on how it works:
pg_dumpruns inside the database container and connects over the local socket, which the official PostgreSQL image trusts, so no password appears on the command line or in the process list.-Fcproduces a compressed “custom format” dump thatpg_restoreunderstands.- The files archive is created by a temporary Alpine container running as root, so the script ends with a
chownback to your user. If you ever create archives yourself withsudo tarin your home folder, they will be owned by root; runsudo chown "$USER": filebefore copying them off the server. - Old backups are deleted after 14 days.
To run it every night at 03:30, add it to your user’s crontab with crontab -e:
30 3 * * * $HOME/outline/backup.sh >> $HOME/outline/backups/backup.log 2>&1
Backups on the same server do not protect you from losing the server. Pull them to another machine regularly, for example from your laptop with rsync -av youruser@your-vps-ip:outline/backups/ ./outline-backups/.
Restoring into a fresh database
The golden rule: restore into an empty database, never on top of the live one. The cleanest test is a second VPS: install Docker (Step 2), copy the folder with the compose file, Caddyfile, .env, outline.env and the backup files, then start only the database and Redis. PostgreSQL creates an empty outline database on first start, and Outline has not touched it yet:
cd ~/outline
docker compose up -d postgres redis
docker compose ps
docker compose exec -T postgres pg_restore -U outline -d outline --no-owner < backups/outline-db-2026-11-10.dump
docker run --rm -v outline_files:/data -v "$PWD/backups":/backup alpine \
tar xzf /backup/outline-files-2026-11-10.tar.gz -C /data
docker compose up -d
Wait until docker compose ps shows postgres as healthy before running pg_restore, and replace the dates with your backup’s date. To restore on the original server instead, first stop Outline so nothing writes to the database, then drop and recreate it empty:
cd ~/outline
docker compose stop outline
docker compose exec -T postgres dropdb -U outline outline
docker compose exec -T postgres createdb -U outline -O outline outline
docker compose exec -T postgres pg_restore -U outline -d outline --no-owner < backups/outline-db-2026-11-10.dump
docker compose start outline
This is destructive by design: everything written since the backup is gone. Restore the files volume the same way if needed, while Outline is stopped. Test your restore at least once; an untested backup is only a hope.
Updating Outline safely
Outline ships new releases often. A safe routine:
- Run
~/outline/backup.shand confirm the files exist. - Read the release notes on the releases page for breaking changes or new required variables.
- If you pinned a version, edit the image tag in
docker-compose.yml. - Pull and restart:
cd ~/outline
docker compose pull
docker compose up -d
docker compose logs -f outline
Outline applies database migrations automatically on startup. When the log settles, test login and open a few documents. Because migrations change the database, going back to an older version means restoring the pre-upgrade backup into a fresh database, as described above. Upgrading PostgreSQL to a new major version (for example 17 to 18) is a separate project: dump the data, start the new major version with a new, empty volume, and restore into it. Note that the PostgreSQL 18 image also changed its default data directory, so the volume mount path changes too.
Troubleshooting and common mistakes
- The Outline container keeps restarting. Run
docker compose logs outline. Messages about missing environment variables point to a typo inoutline.env. “Connection refused” for the database usually means PostgreSQL was not ready yet (the healthcheck should prevent this) orPOSTGRES_PASSWORDwas changed after the database volume was created; the password is only applied on the very first start of an empty volume. - The login page has no login button. No authentication method is configured or a variable name is misspelled. Compare your
OIDC_*orSMTP_*names with Step 6, then rundocker compose up -d --force-recreate outline. - OIDC says “redirect_uri mismatch” or “invalid redirect”. The callback registered in the provider must be exactly
https://your-domain/auth/oidc.callback, andURLinoutline.envmust match the domain users use, withhttpsand no trailing slash. - Login works but you land on an error about email. The provider is not sending an email claim. Add the
emailscope and make sure users have an email address set. - Caddy cannot get a certificate. DNS must point to the VPS, and port 80 must be reachable from the internet. Check
docker compose logs caddy, and verify that nothing else on the host (an old Apache or nginx) already uses ports 80 or 443 withsudo ss -tlnp. - Images fail to upload. If you replaced the named volume with a bind mount (a host folder), that folder must be writable by the Outline user inside the container:
sudo chown -R 1001:1001 /path/to/folder. Also checkFILE_STORAGE_UPLOAD_MAX_SIZE. - Edits do not sync live, or “connection lost” banners appear. WebSockets are not reaching Outline. Caddy handles them automatically; a custom nginx or another proxy in front needs the upgrade headers.
- Magic link emails never arrive. Check the port and
SMTP_SECUREcombination, look atdocker compose logs outlinefor SMTP errors, and check the spam folder. Your sending domain should have SPF and DKIM records for the mail provider you use. - You changed the domain later. Update
URL, theCaddyfileand the OIDC redirect URI together, then recreate the containers. Old links to the previous domain will stop working. - Docker refuses to start in an LXC container. Nesting is not enabled for the container. Ask your provider to enable it, or move to a KVM VPS.
Security notes
- Publish only Caddy. Outline, PostgreSQL and Redis have no
ports:section, so they are not reachable from the internet at all. Resist the temptation to publish 3000 or 5432 “just for testing”: Docker-published ports bypass ufw, so they would be open to everyone. - Protect the secrets.
.envandoutline.envarechmod 600, and so is the backups folder. Never commit them to a Git repository. Keep an offline copy ofSECRET_KEY; without it, encrypted data cannot be read. - Control sign-up. With OIDC, limit access in the provider (groups or application assignments). With email, restrict allowed domains or keep the workspace invite-only, and review the member list from time to time.
- Treat the docker group as root. Anyone in it can control the whole server. Keep SSH key authentication on, disable password logins for SSH, and keep the system updated with
sudo apt update && sudo apt full-upgrade. - Stay current. Outline, Caddy and the database images receive security fixes. Follow the update routine above monthly, or subscribe to Outline’s release notifications on GitHub.
- Keep backups off the server, and encrypt them if they leave infrastructure you control, since they contain every document and the secrets file.
So, is Outline the right wiki for your team?
If you want a fast, good-looking knowledge base with live collaboration, sensible permissions and search that actually finds things, and you would rather keep that knowledge on a server you control, Outline is one of the best choices available today. The only real hurdle is authentication, and once OIDC or SMTP is in place, the daily experience is as smooth as any paid hosted wiki. If you need very complex per-page permissions, offline editing or a strictly OSI-approved license, look at alternatives such as BookStack or Wiki.js instead.
You can run the exact stack from this guide on a VPS in our Romanian data center, with 2 to 4 GB of RAM being plenty for most teams. If you would rather not deal with Docker, certificates, SSO integration and backups yourself, our server administration team can install, secure and maintain Outline for you.
FAQ
Is Outline free to self-host?
Yes. Outline is released under the Business Source License 1.1, which allows you to self-host it for your own organization at no cost. What it forbids is offering Outline as a commercial hosted service that competes with the official one.
Can I use Outline without SSO?
Yes, with email magic links. You need working SMTP settings; users then sign in with a one-time link sent to their inbox. Outline still has no classic passwords, which many teams see as a security advantage.
Should I use local storage or S3 for uploads?
Local storage, as in this guide, is the simplest option and works perfectly for a single server; just include the files volume in your backups. S3-compatible object storage makes sense when you run several Outline instances or have very large amounts of attachments.
Does Outline run in an LXC container?
Docker can run inside LXC only when nesting is enabled for that container, which is not the default for unprivileged containers. A KVM VPS avoids the issue completely.
Can I import content from Notion or Markdown?
Yes. Outline can import Markdown files and exports from Notion, among other formats, from the workspace settings. Large imports temporarily use more RAM and CPU, so run them outside working hours on a small VPS.