Back to all posts

Self-hosted secure file storage - Part I

As many of my friends can tell you, I value my privacy very much and the idea of trusting the big corps with my private data, be it photos, documents, or just some random video I liked in 2015, is something that doesn't work for me.

The idea of my own storage service that I can access anywhere in the world (anywhere with internet :P) does sound nice, and also scary, with all the security implications of opening a pathway to my data over the net. After inordinate time dabbling in Docker containers and sysadmin tools I finally was able to set up a reliable and secure service. This is a multi-part setup and part I focuses on getting this running locally. Let's dive in on how it works.



Tools used:

  • Docker: A platform for running applications in lightweight, isolated containers.

  • Caddy: A web server that automatically manages HTTPS and reverse proxies with minimal configuration.

  • Authelia: A single sign-on (SSO) authentication gateway for securing web applications.

  • Filebrowser: A web-based file manager that lets you browse, upload, and manage files on a server.

Initial setup

First, let's get the easiest part done; setting up the docker-compose. I don't like to mess around with docker run commands since they are relatively unreadable and compose files are the recommended way to set up and orchestrate containers. We will be using images for Caddy, Filebrowser and Authelia, all from the official sources.

Note: All the paths used in the volume mappings are tailored to Windows, so you might need to make adjustments based on what platform you are using.

docker-compose.yaml
1networks:
2  caddy-net:
3
4services:
5  filebrowser:
6    image: filebrowser/filebrowser
7    container_name: filebrowser
8    networks:
9      caddy-net:
10    restart: unless-stopped
11    logging:
12      driver: json-file
13    ports:
14      - "9001:80"
15    volumes:
16      - "a:/docker-configs/filebrowser/config:/config" # Filebrowser settings
17      - "f:/shared:/srv" # Files to browse
18    environment:
19      - PUID=14001                  # Adjust these if needed
20      - PGID=14000
21      - TZ=America/New_York
22
23  authelia:
24    image: authelia/authelia
25    container_name: authelia
26    restart: unless-stopped
27    networks:
28      caddy-net:
29    volumes:
30      - "a:/docker-configs/authelia/config:/config"
31      - "a:/docker-configs/authelia:/var/lib/authelia"
32      - "a:/docker-configs/authelia/secrets:/secrets"
33    environment:
34      - TZ=America/New_York
35    ports:
36      - "9091:9091"  # for initial testing
37
38  caddy:
39    image: caddy:alpine
40    container_name: caddy
41    networks:
42      caddy-net:
43    restart: unless-stopped
44    volumes:
45      - "a:/docker-configs/caddy/Caddyfile:/etc/caddy/Caddyfile"
46      - "a:/docker-configs/caddy/data:/data"
47      - "a:/docker-configs/caddy/config:/config"
48    ports:
49      - "80:80"
50      - "443:443"

We are setting up a shared network for all three containers called caddy-net; this is important for communication between the containers when authenticating.
For initial testing we will be mapping ports for Filebrowser and Authelia to get it working locally. They won't be needed once you use this setup in production.

The next step would be setting up a Caddyfile, which is a config file for the reverse_proxy magic that we need. Here's the initial version for local testing.

Caddyfile
1files.localtest.me {
2    tls internal
3    reverse_proxy filebrowser:80
4}
5
6auth.localtest.me {
7  reverse_proxy authelia:9091
8}
9


In the Caddyfile I'm routing any requests to the auth.localtest.me address to Authelia's exposed port at 9091. Any requests to the files.localtest.me are routed to Filebrowser's port 80. The tls internal directive generates certs for the paths, so converting http -> https when testing in local dev.

The paths I'm using here, auth.localtest.me and files.localtest.me are aliases for localhost that I set up in the etc/hosts file for the OS. Look these up if you are unfamiliar. The reason for doing this will become clear in the next step: Authelia configuration. For now, run the docker containers using the command docker-compose up -d in the directory where your docker-compose is. Make sure the Caddyfile is at the location you mapped in the docker-compose file. Hit the two paths in your browser and if everything's set up fine till now the Filebrowser and Authelia login screens should show up. You can login to Filebrowser using admin as the username as well as password, but a login to Authelia requires a bit more setup.

Adding an Authelia sign-in

Let's protect the file path using Authelia. We want to forward all requests to the files.localtest.me path to an Authelia sign-in screen and then redirect to a validated https path before reverse proxying to the Filebrowser port. The updates to the Caddyfile below achieves that.

Caddyfile
1files.localtest.me {
2  tls internal
3  forward_auth * http://authelia:9091 {
4    uri /api/authz/forward-auth?redirect_url=https://auth.localtest.me
5    copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
6  }
7  reverse_proxy http://filebrowser:80
8}
9
10auth.localtest.me {
11  tls internal
12  reverse_proxy http://authelia:9091
13}

The next part would be telling Authelia how to handle these forwarded auth requests. Create a configuration.yml to add the code below. Make sure it's .yml, and not .yaml. I found out there's no YAML support after a ludicrous amount of debugging to see why my config wouldn't work.

configuration.yml
1theme: auto
2
3server:
4  host: 0.0.0.0 # localhost
5  port: 9091
6  buffers:
7    read: 16384 # The default buffer size of 4096 doesn't work for local redirection, so use sufficient sizes here.
8    write: 16384
9
10identity_validation:
11  reset_password:
12    jwt_secret: ""  # Use something random
13
14authentication_backend:
15  file:
16    path: /config/users_database.yml # Stores usernames and passwords for all users allowed through Authelia auth
17
18access_control:
19  default_policy: deny
20  rules:
21  - domain: "auth.localtest.me" # This should be your authentication URL
22    policy: bypass # Do not ask for credentials on the auth page
23  - domain: "files.localtest.me" # Example domain to protect
24    policy: one_factor
25
26log:
27  level: debug
28
29session:
30  name: authelia_session
31  secret: ""
32  expiration: 3600  # 1 hour
33  inactivity: 300  # 5 minutes
34  cookies:
35      - domain: localtest.me              
36        authelia_url: https://auth.localtest.me # Make sure to use https here, as auth cookies can only be set on a secure domain
37
38regulation:
39  max_retries: 3 # Prevent brute force logins
40  find_time: 2m
41  ban_time: 30m
42
43storage:
44  encryption_key: ""
45  local:
46    path: /var/lib/authelia/db.sqlite3
47
48notifier:
49  filesystem:
50    filename: /config/notification.txt # Location where password reset emails would be sent unless SMTP is configured to send actual emails
51

Wondering how to get the values for jwt_secret, secret, and encryption_key? You can generate secure random strings by running:

openssl rand -hex 32

You will also need a user_database.yml file which is what Authelia uses to store credentials, unless you integrate with an LDAP provider. This is what it looks like:

users_database.yml
1users:
2  testusername:
3    displayname: "Test User"
4    password: "$argon2id$v=19$m=65536,t=3,p=2$U3VwZXJTZWNyZXQ$gWtf7R5a2b0u7uJKVDJvZQ"  # password: "password"
5    email: testusername@example.com
6    groups:
7      - admins
8

You can generate a super-secure password by running:

docker run --rm -it authelia/authelia:latest authelia crypto hash generate argon2

Once all this is in place, restart your docker containers. Trying to load the files path should now redirect you to the Authelia sign-in, where you can use your username and password. Remember to use your unhashed password here.


That's all for this part of the self-hosted storage service series. You should now have:

  1. A Caddy reverse proxy for routing requests to Authelia or Filebrowser containers

  2. An Authelia setup for guarding access to your Filebrowser service

  3. A secure self-hosted storage for your data in Filebrowser

In the next part I will cover how to expand this setup to use a custom domain and tunneling over the internet to your data.

Share this post: