Teams and individuals running their own infrastructure often lose track of subscription spending — monthly SaaS fees, annual licenses, cloud service costs, and streaming accounts accumulate quietly until a budget review reveals hundreds of dollars in forgotten charges. Wallos is a lightweight, privacy-first subscription tracker that runs entirely on your own server. It gives you a live dashboard of all recurring costs, renewal dates, and payment totals without sharing your financial data with any third party. Built with PHP and SQLite, it requires no external database, runs in a single Docker container using under 60 MB of memory, and pairs cleanly with any reverse proxy that supports automatic TLS.
This guide deploys a production-ready Wallos instance on Ubuntu 22.04 LTS using Docker Compose for container lifecycle management and Caddy for automatic HTTPS. By the end you will have a publicly accessible Wallos dashboard with TLS, persistent SQLite storage for your subscription data and logo assets, environment-based configuration, and a secured admin account ready for daily use across your household or small team.
Architecture and flow overview
Wallos runs as a PHP application inside a single container backed by a SQLite database file stored on a named Docker volume. There is no separate database service — SQLite handles all persistence including subscription records, user accounts, billing cycle data, and historical spending totals. This makes the deployment simple to backup (a single file copy), trivial to restore on a new server, and lightweight enough to run alongside multiple other services on a single VPS without meaningful resource contention.
Traffic flow: a browser connects to Caddy on port 443. Caddy terminates TLS using an automatically provisioned Let's Encrypt certificate and reverse-proxies the request to the Wallos container listening on localhost port 8282. Caddy's automatic HTTP-to-HTTPS redirect and HSTS headers are enabled by default with no additional configuration required. The Docker Compose stack binds the container exclusively to the loopback interface (127.0.0.1:8282), meaning no traffic reaches Wallos directly from the public internet — all external access is mediated by Caddy. A second named volume holds user-uploaded subscription logo images separately from the database, so logo assets survive container image updates without any manual intervention.
Prerequisites
- Ubuntu 22.04 LTS server with root or sudo access
- Minimum 512 MB RAM and 1 vCPU (Wallos uses under 60 MB memory at idle)
- Docker Engine 24+ and Docker Compose Plugin installed
- Caddy 2.7+ installed as a systemd service
- A domain name (e.g.,
wallos.yourdomain.com) with an A record pointing to the server's public IP, fully propagated before starting Caddy - UFW or equivalent firewall with inbound ports 22 (SSH), 80 (Caddy ACME), and 443 (HTTPS) allowed; all other ports denied by default
- Port 8282 not already bound on the host by another service
Step-by-step deployment
1. Install Docker Engine and Compose Plugin
Install Docker from the official repository to get the current Engine and Compose Plugin versions, not the older docker.io package bundled with Ubuntu's default APT sources.
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) \
signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| sudo tee /etc/apt/sources.list.d/docker.list
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo systemctl enable --now docker2. Install Caddy from the official repository
Install Caddy using the official Cloudsmith repository to ensure you receive automatic package updates and the current stable release with all standard modules included.
sudo apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt-get update
sudo apt-get install -y caddy3. Create the project directory
Place all Wallos stack files under a single directory to make future backups, updates, and migrations straightforward.
sudo mkdir -p /opt/wallos
sudo chown $USER:$USER /opt/wallos
cd /opt/wallos4. Write the Docker Compose file
The Compose file runs a single Wallos container with two named volumes — one for the SQLite database file and one for subscription logo images. Binding only to 127.0.0.1:8282 ensures the application is not reachable from outside the host without going through Caddy.
version: "3.8"
services:
wallos:
image: ellite/wallos:latest
container_name: wallos
restart: unless-stopped
ports:
- "127.0.0.1:8282:80"
environment:
- TZ=${TZ:-UTC}
volumes:
- wallos_db:/var/www/html/db
- wallos_logos:/var/www/html/images/uploads/logos
volumes:
wallos_db:
wallos_logos:5. Create the environment file
Store the timezone variable in a dedicated environment file. This separates runtime configuration from the Compose definition and simplifies future credential additions such as SMTP settings for renewal reminders.
cat > /opt/wallos/.env <<'EOF'
TZ=UTC
EOF
chmod 600 /opt/wallos/.env6. Configure the Caddyfile
Add a site block for your Wallos domain to /etc/caddy/Caddyfile. Caddy automatically provisions and renews the TLS certificate from Let's Encrypt and handles HTTP-to-HTTPS redirection with no additional directives.
wallos.yourdomain.com {
reverse_proxy 127.0.0.1:8282
}Reload Caddy to apply the new site block:
sudo systemctl reload caddy7. Start the Wallos stack
Pull the image and start the container in detached mode. The first startup initializes the SQLite database file on the named volume and launches the PHP application server.
cd /opt/wallos
docker compose up -d
docker compose logs -f wallosWait for Apache startup messages confirming the PHP application is ready. Open https://wallos.yourdomain.com in a browser and complete the initial setup wizard to create your admin account and configure your base currency, timezone, and exchange rate preferences.
Configuration and secrets handling
Wallos does not expose sensitive credentials through environment variables — the admin account, password, and application preferences are configured through the web UI during the first-run setup wizard. The only runtime configuration in your stack is the timezone string in .env. If you later enable SMTP email notifications for renewal reminders, add those credentials to .env and reference them in the Compose file as environment variables; never commit the file to a version control repository.
Restrict access to the configuration directory on the host to prevent other local users or processes from reading the environment file:
chmod 700 /opt/wallos
chmod 600 /opt/wallos/.env
chmod 600 /opt/wallos/docker-compose.ymlConfirm your UFW firewall rules block direct access to the Wallos port and only allow the expected public-facing ports:
sudo ufw allow 22/tcp comment "SSH"
sudo ufw allow 80/tcp comment "Caddy ACME"
sudo ufw allow 443/tcp comment "HTTPS"
sudo ufw deny 8282/tcp comment "Wallos internal only"
sudo ufw --force enable
sudo ufw status verboseVerification
- Open
https://wallos.yourdomain.comin a browser. Confirm you see the Wallos login page served over HTTPS with a valid Let's Encrypt certificate in the browser's security indicator. - Log in with the admin credentials you created during setup. The main dashboard should load showing zero subscriptions, your selected base currency, and upcoming renewals as an empty state.
- Add a test subscription entry (e.g., a monthly service at any amount) and confirm it appears in the dashboard with the correct next renewal date and monthly cost calculation.
- Restart the container and verify the test subscription persists:
docker compose restart wallosthen reload the dashboard. - Confirm Caddy is redirecting HTTP to HTTPS:
curl -I http://wallos.yourdomain.comshould return a redirect response pointing to the HTTPS URL. - Verify the data volume is mounted and populated:
docker volume inspect wallos_wallos_dbshould show a Mountpoint under/var/lib/docker/volumes/with a non-zero directory size.
Common issues and fixes
Caddy cannot obtain a certificate — ACME challenge fails: Verify your domain's A record resolves to this server's public IP with dig A wallos.yourdomain.com. Confirm UFW allows inbound port 80, which Let's Encrypt requires for the HTTP-01 challenge. Inspect Caddy logs with sudo journalctl -u caddy -n 50 for the specific ACME error message and remediate accordingly.
503 Bad Gateway from Caddy: The Wallos container is not responding on port 8282. Run docker compose ps to confirm the container is running. Check application logs with docker compose logs wallos for PHP or Apache startup errors. Verify the port is actually bound on the host with ss -tlnp | grep 8282.
Container restarts in a loop after an update: Pin a specific image tag in docker-compose.yml (e.g., ellite/wallos:2.0.0) instead of :latest when a new release introduces a regression. Check the Wallos GitHub releases for known issues before pulling a major version bump. Inspect logs with docker compose logs --tail 50 wallos for the specific PHP or database error triggering the restart loop.
Renewal reminder emails not delivering: Wallos requires SMTP configuration through the Settings panel in the web UI. Verify you entered valid SMTP credentials, the correct port (587 for STARTTLS, 465 for SMTPS), and your server's outbound SMTP is not blocked by a cloud provider firewall rule. Test connectivity with nc -zv smtp.yourprovider.com 587 before debugging the application configuration.
Logo images not displaying for subscriptions: Confirm the wallos_wallos_logos volume is mounted and the container user has write access. The Wallos image runs as www-data; Docker named volumes are managed by the Docker daemon and should not produce permission errors unless you have manually altered the volume directory permissions. Run docker exec wallos ls -la /var/www/html/images/uploads/logos to inspect permissions from inside the container.
FAQ
How do I back up Wallos data?
All subscription data lives in a single SQLite file on the wallos_wallos_db named volume. Find the host path with docker volume inspect wallos_wallos_db | grep Mountpoint, then copy the wallos.db file to secure offsite storage. Logo images are stored on the wallos_wallos_logos volume and can be backed up the same way. For automated daily backups, combine a cron job with rsync or rclone to push both volume directories to an S3-compatible bucket or remote server.
Does Wallos support multiple currencies?
Yes. During the initial setup wizard you select a base display currency. Individual subscriptions can be entered in any supported currency — Wallos converts them to the base currency using exchange rate data fetched from a configurable provider. You can track spending in EUR, USD, GBP, and dozens of additional currencies simultaneously. The dashboard totals reflect converted amounts in your base currency so you can see your true monthly and annual spend at a glance regardless of how many different currencies you pay in.
Can I share Wallos with family members or a small team?
Yes. Wallos supports multiple user accounts. Each user can track their own subscriptions independently, or an admin can create a shared household view. Create additional accounts in the Users section of the admin panel. Each member gets a separate login and sees only their own entries by default, while the admin account has access to an aggregated spending overview across all users in the instance.
How do I upgrade Wallos to a newer version?
Pull the updated image and recreate the container. Named volumes are preserved across the operation so no data is lost.
cd /opt/wallos
docker compose pull
docker compose up -d --force-recreateReview the GitHub releases page for migration notes or schema changes before upgrading between major version numbers.
Can I set up renewal reminders before a subscription charges?
Yes. Go to Settings in the Wallos web UI and configure your SMTP credentials under the email notification section. Once email is working, each subscription entry has a reminder field where you specify how many days before the renewal date you want a notification sent. You can configure multiple reminder intervals — for example 7 days and 1 day before renewal — so you have time to cancel a subscription you no longer need before the next billing cycle hits your account.
Can I import subscriptions from a spreadsheet or another tool?
Yes. Wallos supports CSV import for bulk entry. Navigate to the Import section in the web UI and upload a CSV file with columns matching the Wallos template (subscription name, price, billing cycle, next renewal date, currency). You can also export existing data to CSV for backup or migration to another instance. For migration from a spreadsheet, map your columns to the Wallos CSV format using the downloadable template available in the Settings panel.
Can I run Wallos on a private local network without a public domain?
Yes. For LAN-only use, bind Wallos directly to a host interface (e.g., change the port binding to 0.0.0.0:8282:80) and access it via your server's local IP address and port over plain HTTP. This is acceptable for a trusted private network. Alternatively, use Caddy's tls internal directive to generate a self-signed certificate for a local hostname, then add the Caddy root CA to your browser's trusted certificate store for HTTPS on the internal network without Let's Encrypt.
Internal links
- Production Guide: Deploy ntfy with Docker Compose + Caddy — pair Wallos renewal reminders with a self-hosted push notification server as an alternative to email alerts.
- Production Guide: Deploy Glance with Docker Compose + Caddy — embed your subscription spending overview alongside other self-hosted service dashboards in a unified home panel.
- Deploy Actual Budget with Docker Compose and Traefik — complement subscription tracking with a full personal finance and zero-based budgeting tool on the same server.
Talk to us
If you want this deployed with hardened access controls, monitoring standards, and production runbooks tailored to your environment, our team can help end-to-end.