This documentation chronicles my personal journey setting up a secure Docker environment using Saltbox, Traefik, Authelia, and Cloudflare. It captures my exact process with all the trial, error, and discoveries along the way - not an official guide, but what worked for me. If there's anything missing or unclear, feel free to reach out for clarification.
My Docker server configuration is highly customized for security, ease of management, and Cloudflare integration. I've combined Saltbox's robust automation with Viren070's flexible Docker Compose template to create a streamlined environment where containers are protected behind Authelia SSO, routed through Traefik reverse proxy, and secured with Cloudflare DNS and SSL certificates.
💡 Core Philosophy: Prioritize security and automation while maintaining flexibility. Every component serves a purpose - Saltbox handles the heavy lifting, Traefik manages routing, Authelia secures access, and Cloudflare protects your infrastructure.
You'll need a server with a fresh install of Ubuntu Server:
⚠️ ARM Architecture Limitation: This tutorial will not work on Oracle Ampere Instances as ARM is not supported with Saltbox. If you're using Oracle Ampere, refer to prerequisite #5 and follow the alternative setup method.
💡 Hardware Note: These are my recommendations based on running multiple containers. You can try lower specs, but performance may vary depending on your workload.
You'll need a domain name for this setup. Here are affordable options:
.xyz domain for less than $1/year🌟 1.11B XYZ Domain Trick: You can register numeric-only
.xyzdomains (like123456789.xyz) for under $1/year. See pricing example.
You must install Saltbox on a fresh machine before proceeding:
🔧 Oracle Ampere Alternative: If you're using an Oracle Ampere instance, install Docker and Viren's compose template using this guide. Don't follow my Traefik label instructions - use Viren's tutorial directly instead.
Assuming you've successfully installed Saltbox and logged in with the user you created during setup, we'll start by cloning Viren070's template.
Run the following commands:
cd /opt
git clone https://github.com/Viren070/docker-compose-vps-template.git docker
cd docker
This clones the repository into /opt/docker. You can use a different location, but remember to update the path in /opt/docker/.env accordingly.
📁 Directory Structure: The
/opt/dockerfolder will become the central hub for all your Docker containers and configurations.
We'll need your user ID for proper file permissions. Run:
id -u
If you ran the Saltbox script correctly, the output should look like:
kuu@kuu:~$ id -u
1000
📝 Note the Output: Save this number - you'll need it for the next step.
Open the .env file for editing (assuming you're still in /opt/docker):
nano .env
Find and edit the timezone variable:
TZ=Etc/UTC
If you're not in UTC, find your timezone from the TZ database list.
For example, I set mine to:
TZ=Africa/Timbuktu
Locate PUID= and PGID= and set them to the number from your id -u command earlier (unless they're already set correctly):
PUID=1000
PGID=1000
Find and edit the compose profiles variable:
COMPOSE_PROFILES="required"
Remove "required" so it becomes:
COMPOSE_PROFILES=""
💡 Why Remove "required"?: Saltbox already installed Traefik and Authelia for us. We don't need the required profile anymore, but we'll use this variable later when running specific services.
Replace the example domain with your actual domain:
DOMAIN=example.com
Change to:
DOMAIN=yourdomain.com
Comment out the following variables by adding # at the beginning of each line (these conflict with Saltbox's setup):
# LETSENCRYPT_EMAIL=
# AUTHELIA_SESSION_SECRET=""
# AUTHELIA_STORAGE_ENCRYPTION_KEY=""
# AUTHELIA_JWT_SECRET=""
⚠️ Why Comment These Out?: Saltbox uses a different SSL certificate generation method and manages Authelia configuration separately. These variables would create conflicts.
You can provide the CLOUDFLARE_API_TOKEN if you want to use cloudflare-ddns to automatically add DNS records to Cloudflare. Follow the guidance provided in the .env file.
🔄 Manual vs Automatic DNS: I recommend adding DNS records manually for better control. Make sure the
Arecords match the variables in the .env file, so for exampleAIOSTREAMS_HOSTNAME=aiostreams.${DOMAIN}will beaiostreams.yourdomain.comon CloudFlare. If you do enable automatic DNS creation, make sure to removeAUTHELIA_HOSTNAMEandTRAEFIK_HOSTNAMEfrom/opt/docker/apps/cloudflare-ddns/compose.yamlto avoid conflicts (Saltbox already created these subdomains).
Below you'll see a list of hostname variables like:
ACTUAL_BUDGET_HOSTNAME=actual-budget.${DOMAIN?}
ADDON_MANAGER_HOSTNAME=addon-manager.${DOMAIN?}
ADGUARD_HOSTNAME=adguard.${DOMAIN?}
AIOLISTS_HOSTNAME=aiolists.${DOMAIN?}
AIOMETADATA_HOSTNAME=aiometadata.${DOMAIN?}
AIOSTREAMS_HOSTNAME=aiostreams.${DOMAIN}
These are used in .env files and compose.yaml as an easier way to direct your URLs. Do not change ${DOMAIN?}, that will automatically be replaced by the domain you set earlier.
💡 Adding Custom Services: When you want to host a custom service (like Yamtrack for example), add its hostname here:
YAMTRACK_HOSTNAME=yamtrack.${DOMAIN}
Then referenceYAMTRACK_HOSTNAMEin your compose file for Traefik labels or environment variables.
You need to also create theArecord in your CloudFlare DNS in this case it would beyamtrack.yourdomain.com
Find the "DOCKER COMPOSE CONFIGURATION" section at the end of the file. Change:
DOCKER_NETWORK=aio_network
DOCKER_NETWORK_EXTERNAL=false
To:
DOCKER_NETWORK=saltbox
DOCKER_NETWORK_EXTERNAL=true
🌐 Why Use Saltbox Network?: This allows your containers to communicate with Saltbox-created containers and uses the existing network infrastructure instead of creating a new one.
If you're using nano: press Ctrl + X, then type y, then press Enter to save.
Open the main compose file:
nano /opt/docker/compose.yaml
This is the main compose file - all app compose files need to be referenced here so that profiles, hostnames, and other variables get loaded when running your containers.
📋 Adding Custom Containers: If you're adding a custom container, reference it here. For example, with Yamtrack:
- apps/yamtrack/compose.yaml
Make sure this folder and compose file exist before running any commands.
Find the networks section at the bottom of the file:
networks:
default:
name: ${DOCKER_NETWORK:-aio_default}
external: ${DOCKER_NETWORK_EXTERNAL:-false}
Change it to:
networks:
default:
name: ${DOCKER_NETWORK:-saltbox}
external: ${DOCKER_NETWORK_EXTERNAL:-true}
🔧 Fallback Configuration: This ensures the network always falls back to
saltboxinstead ofaio_defaultif the variable cannot be found, and always treats it as an external network.
⚠️ Oracle Ampere Users: If you're using an Ampere instance, follow Viren's guide and set up the template as directed. You won't need to make any of these changes - set up Traefik and Authelia using his method instead of Saltbox.
Now we have a solid server and Docker environment that can be automated and managed easily. However, because we're using Saltbox, we need to modify the Traefik labels in the template's compose files.
💡 Why Modify Labels?: Saltbox operates Traefik slightly differently than Viren's compose template. We're using Viren's template as a base for compose files and variables, but adapting the Traefik configuration to work with Saltbox.
With Saltbox, you can create your own containers following this guide. However, the easiest method is using the Traefik Template Generator: Documentation
I'll demonstrate the process using AIOStreams as an example. This same process applies to any container you want to configure.
cd /opt/docker/apps/aiostreams
nano compose.yaml
In the compose file, locate and note the following:
labels: section (e.g., ${AIOSTREAMS_HOSTNAME?})expose: section (e.g., 3000)container_name: field (e.g., aiostreams)Exit the editor after noting these details.
Execute the following command:
sb install generate-traefik-template
The generator will ask several questions. Here's how to answer for AIOStreams:
Question 1: Please enter a unique name for your application:
Type the container_name from the compose file:
aiostreams
Question 2: Please enter the port used for the application's WEB UI:
Type the port from expose: in the compose file:
3000
Question 3: Do you want the application to be behind SSO (Authelia/Authentik)? (yes/no):
yes if you want the application protected by login (recommended for private use)no if you want it publicly accessible🔐 SSO Consideration: If you want secure, private access, leave it behind Authelia. You'll log in using the username and password you set up during Saltbox configuration. Some services may not work properly with Authelia enabled - research specific compatibility if issues arise.
Question 4: Do you want the application to have an API router? (yes/no):
Type no for most cases.
🔧 API Router Note: Only needed if you're publicly hosting your instance and want to manage specific endpoints separately. Research this feature if you need advanced routing control.
View the generated compose file:
cat /tmp/docker-compose.yml
Copy the labels section from the output. For this example, it looks like:
labels:
com.github.saltbox.saltbox_managed: true
diun.enable: true
autoheal: true
autoheal.stop.timeout: 10
traefik.enable: true
traefik.http.routers.aiostreams-http.entrypoints: web
traefik.http.routers.aiostreams-http.service: aiostreams-http
traefik.http.routers.aiostreams-http.rule: Host(`aiostreams.example.com`)
traefik.http.routers.aiostreams-http.priority: 20
traefik.http.routers.aiostreams-http.middlewares: globalHeaders@file,redirect-to-https@docker,robotHeaders@file,authelia@docker
traefik.http.routers.aiostreams.entrypoints: websecure
traefik.http.routers.aiostreams.service: aiostreams
traefik.http.routers.aiostreams.rule: Host(`aiostreams.example.com`)
traefik.http.routers.aiostreams.priority: 20
traefik.http.routers.aiostreams.tls.certresolver: cfdns
traefik.http.routers.aiostreams.tls.options: securetls@file
traefik.http.routers.aiostreams.middlewares: globalHeaders@file,secureHeaders@file,robotHeaders@file,authelia@docker
traefik.http.services.aiostreams-http.loadbalancer.server.port: 3000
traefik.http.services.aiostreams.loadbalancer.server.port: 3000
Open the compose file again:
nano /opt/docker/apps/aiostreams/compose.yaml
labels: section with the generated labelsexpose: section (Traefik handles ports directly)aiostreams.example.com to ${AIOSTREAMS_HOSTNAME?} in both rule locationsYour final compose.yaml should look like:
services:
aiostreams:
image: ghcr.io/viren070/aiostreams:${AIOSTREAMS_TAG:-latest}
container_name: aiostreams
restart: unless-stopped
env_file:
- .env
labels:
com.github.saltbox.saltbox_managed: true
diun.enable: true
autoheal: true
autoheal.stop.timeout: 10
traefik.enable: true
traefik.http.routers.aiostreams-http.entrypoints: web
traefik.http.routers.aiostreams-http.service: aiostreams-http
traefik.http.routers.aiostreams-http.rule: Host(`${AIOSTREAMS_HOSTNAME?}`)
traefik.http.routers.aiostreams-http.priority: 20
traefik.http.routers.aiostreams-http.middlewares: globalHeaders@file,redirect-to-https@docker,robotHeaders@file,authelia@docker
traefik.http.routers.aiostreams.entrypoints: websecure
traefik.http.routers.aiostreams.service: aiostreams
traefik.http.routers.aiostreams.rule: Host(`${AIOSTREAMS_HOSTNAME?}`)
traefik.http.routers.aiostreams.priority: 20
traefik.http.routers.aiostreams.tls.certresolver: cfdns
traefik.http.routers.aiostreams.tls.options: securetls@file
traefik.http.routers.aiostreams.middlewares: globalHeaders@file,secureHeaders@file,robotHeaders@file,authelia@docker
traefik.http.services.aiostreams-http.loadbalancer.server.port: 3000
traefik.http.services.aiostreams.loadbalancer.server.port: 3000
volumes:
- ${DOCKER_DATA_DIR}/aiostreams:/app/data
profiles:
- aiostreams
- all
🔓 Removing Authelia Protection: To make a service publicly accessible without login, remove
authelia@dockerfrom the middlewares (including the comma). The middleware label would become:
traefik.http.routers.aiostreams.middlewares: globalHeaders@file,secureHeaders@file,robotHeaders@file
As a precaution, set the correct permissions for the data folder:
sudo chown 1000:1000 -R /opt/docker/data
🔧 Custom User ID: Change
1000:1000to match your user ID from theid -ucommand earlier, the same as what you set inPUIDandPGIDin the main.envfile.
Navigate back to the main Docker directory:
cd /opt/docker
nano .env
Find and edit the COMPOSE_PROFILES variable to include your service:
COMPOSE_PROFILES="aiostreams"
📋 Profile Names: Use whatever label is set in the
compose.yamlunder theprofiles:section. This allows mass commands to bring up all configured services at once.
Navigate to the apps directory:
cd /opt/docker/apps
⚠️ Important: You must be in this folder so that the variables pick up correctly from both the specific container's
.envandcompose.yamlaswell as the main/opt/docker/.envand/opt/docker/compose.yaml.
Start your container using one of these methods:
Method 1 - Start all services in COMPOSE_PROFILES:
docker compose up -d
Method 2 - Start a specific service:
docker compose --profile aiostreams up -d
Make sure you've set up your Cloudflare subdomain to point to your server's external IP.
aiostreams.yourdomain.com pointing to your server's external IP🔄 Automatic DNS: If you set up cloudflare-ddns earlier, this subdomain should have been created automatically.
aiostreams.yourdomain.com to confirm it's online🎉 Success: Your service is now running behind Cloudflare's CDN and DDoS protection, with Traefik handling routing and Authelia (if enabled) securing access!
Congratulations! You've set up your server the way I set it up - a very roundabout method, but it's what I find best for combining:
🔄 Repeat for Other Services: Use this same process (Steps 6-10) for any other container you want to add. The workflow becomes much faster once you're familiar with it!
💡 Ongoing Maintenance: This setup is designed to be low-maintenance. Containers auto-update with Diun, Traefik handles routing automatically, and Authelia maintains security. The hardest part is the initial configuration - after that, it runs smoothly!