
This article provides a guide demonstrating how to deploy Wallos on Ubuntu VPS with Docker Compose.
What Is Wallos?

Wallos is a free, open-source, self-hosted subscription management application that helps individuals and businesses track recurring expenses, monitor upcoming payments, and manage subscriptions from a centralized dashboard.
Unlike cloud-based subscription tracking services, Wallos can be installed on your own server, giving you greater control over your financial information and application data.
Key Features
- Subscription Tracking: Manage recurring expenses such as web hosting, VPS servers, domain registrations, software licenses, SaaS platforms, streaming services, and memberships.
- Payment Reminders: Receive notifications about upcoming renewals and recurring payments.
- Expense Analytics: Visualize monthly and annual subscription spending using charts and dashboards.
- Multiple Currencies: Track subscriptions billed in different currencies.
- Custom Categories: Organize subscriptions into categories such as Hosting, Infrastructure, Software, Business Services, and Entertainment.
- Payment Methods: Associate subscriptions with payment methods for easier expense management.
- Multiuser Support: Manage subscriptions for multiple users.
- Self-Hosted Privacy: Store your subscription information on infrastructure you control.
Technical Requirements
Wallos is designed to be lightweight and relatively easy to deploy.
| Component | Technology |
|---|---|
| Application | PHP |
| Database | SQLite |
| Web server | Apache or Nginx |
| Deployment | Docker Compose or native installation |
| Operating systems | Linux, including Ubuntu and Debian |
| License | Open source |
| Cost | Free |
Who Should Use Wallos?
Wallos is particularly useful for individuals and organizations managing numerous recurring expenses.
In short, Wallos provides a private, centralized way to answer three important questions: What am I paying for, how much am I spending, and when is my next payment due?
This guide deploys Wallos as a production-ready, self-hosted application on an Ubuntu VPS, with secure HTTPS access, persistent SQLite storage, scheduled backups, and a controlled update process.
The installation follows Wallosβs official Docker deployment structure, with additional hardening for an Internet-facing VPS.
Deploy Wallos on Ubuntu VPS with Docker Compose
To deploy Wallos on Ubuntu VPS with Docker Compose, follow the steps outlined below:
-
Deployment architecture
Deployment settings
Component Configuration OS Ubuntu Server 22.04 or 24.04 Application Wallos Docker image bellamy/wallos:latestPublic access HTTPS port 443 Internal service 127.0.0.1:8282Reverse proxy Host-installed Nginx SSL Letβs Encrypt / Certbot Database SQLite Application directory /opt/wallosBackup directory /var/backups/wallosTimezone America/ChicagoFor a dedicated small VPS, 1β2 vCPUs, 2 GB RAM, and approximately 20 GB of storage is a reasonable starting point. These are planning recommendations, not official Wallos minimum requirements.
Compare Ubuntu VPS Plans
KVM-SSD-1KVM-SSD-8KVM-SSD-16KVM-SSD-32CPU1 Core2 Cores4 Cores8 CoresMemory1 GB8 GB16 GB32 GBStorage16 GB NVMe128 GB NVMe256 GB NVMe512 GB NVMeBandwidth1 TB4 TB8 TB16 TBNetwork1 Gbps1 Gbps1 Gbps1 GbpsDelivery Timeβ±οΈ Instantβ±οΈ Instantβ±οΈ Instantβ±οΈ InstantLocationUS/EU/APACUS/EU/APACUS/EU/APACUS/EU/APACPrice$7.58*$39.50*$79.40*$151.22*KVM-SSD-1$7.58*CPU 1 CoreMemory 1 GBStorage 16 GB NVMeBandwidth 1 TBNetwork 1 GbpsDelivery Time β±οΈ InstantLocation US/EU/APACKVM-SSD-8$39.50*CPU 2 CoresMemory 8 GBStorage 128 GB NVMeBandwidth 4 TBNetwork 1 GbpsDelivery Time β±οΈ InstantLocation US/EU/APACKVM-SSD-16$79.40*CPU 4 CoresMemory 16 GBStorage 256 GB NVMeBandwidth 8 TBNetwork 1 GbpsDelivery Time β±οΈ InstantLocation US/EU/APACKVM-SSD-32$151.22*CPU 8 CoresMemory 32 GBStorage 512 GB NVMeBandwidth 16 TBNetwork 1 GbpsDelivery Time β±οΈ InstantLocation US/EU/APAC
-
Prepare Ubuntu
Run the commands as
root, or usesudo -ifirst.sudo -i
Check the environment:
cat /etc/os-release uname -m hostnamectl df -h / free -h
Update installed packages:
apt-get update apt-get upgrade -y
Install utilities:
apt-get install -y \ ca-certificates \ curl \ gnupg \ openssl \ tar \ gzip \ sqlite3 \ nginx \ certbot \ python3-certbot-nginx \ ufw \ unattended-upgrades
Enable Nginx:
systemctl enable --now nginx
If an existing application uses TCP ports 80 or 443, inspect the active virtual hosts before proceeding.
-
Install Docker Engine and Compose
Weβll use Dockerβs official Ubuntu APT repository, rather than the convenience installation script. This follows Dockerβs documented production installation approach.
First, check for existing installations:
docker --version 2>/dev/null || true docker compose version 2>/dev/null || true dpkg -l | grep -E \ 'docker|containerd|podman' || true
If this is a fresh VPS, continue. If Docker is already hosting other workloads, inspect the packages before making changes.
On a fresh server, remove conflicting packages:
apt-get remove -y \ docker.io \ docker-compose \ docker-compose-v2 \ docker-doc \ docker-buildx \ podman-docker \ containerd \ runc
Install the repository signing key:
install -m 0755 -d /etc/apt/keyrings curl -fsSL \ https://download.docker.com/linux/ubuntu/gpg \ -o /etc/apt/keyrings/docker.asc chmod a+r /etc/apt/keyrings/docker.asc
Create the APT source:
.Β /etc/os-release cat > /etc/apt/sources.list.d/docker.sources <<EOF Types: deb URIs: https://download.docker.com/linux/ubuntu Suites: $(. /etc/os-release && echo "\${UBUNTU_CODENAME:-\$VERSION_CODENAME}") Components: stable Architectures: $(dpkg --print-architecture) Signed-By: /etc/apt/keyrings/docker.asc EOFInstall Docker:
apt-get update apt-get install -y \ docker-ce \ docker-ce-cli \ containerd.io \ docker-buildx-plugin \ docker-compose-plugin
Enable the Docker service:
systemctl enable --now docker systemctl enable --now containerd
Verify the installation:
docker version docker compose version systemctl is-active docker
Test Docker:
docker run --rm hello-world
The test should print a message confirming that Docker Engine is working.
-
Create Wallosβs application directories
Create the application and storage directories:
install -d -m 0750 /opt/wallos install -d -m 0755 /opt/wallos/db install -d -m 0755 /opt/wallos/logos install -d -m 0700 /var/backups/wallos cd /opt/wallos
The persistent directory layout will be:
/opt/wallos/ βββ compose.yaml βββ db/ β βββ wallos.db βββ logos/ /var/backups/wallos/ βββ wallos-YYYY-MM-DD_HHMMSS.tar.gz
Wallos officially uses two bind-mounted locations: the SQLite database directory and uploaded subscription logos.
-
Create the Docker Compose configuration
Create the Compose file:
See Also: How to Install and Run Open Source Feedback Portal to Gather Feature Requests
cat > /opt/wallos/compose.yaml <<'YAML' services: wallos: image: bellamy/wallos:latest container_name: wallos restart: unless-stopped init: true ports: - "127.0.0.1:8282:80/tcp" environment: TZ: America/Chicago volumes: - ./db:/var/www/html/db - ./logos:/var/www/html/images/uploads/logos security_opt: - no-new-privileges:true logging: driver: json-file options: max-size: "10m" max-file: "5" stop_grace_period: 30s YAMLThis configuration adds several protections over the default example:
- Docker binds only to localhost, preventing direct public access to port 8282.
no-new-privilegesprevents processes from gaining additional privileges through setuid/setgid execution.- Log rotation prevents uncontrolled Docker JSON log growth.
- Persistent bind mounts preserve database and uploaded logos.
- Docker automatically restarts Wallos after a daemon or server restart.
We deliberately do not use
privileged: true, host networking, or a mounted Docker socket.Validate the Compose file:
cd /opt/wallos docker compose config --quiet
Start the application:
docker compose pull docker compose up -d
Check the container:
docker compose ps docker inspect wallos --format \ '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}'Check logs:
docker compose logs --tail=100 wallos
Test HTTP from the server:
curl -sSI http://127.0.0.1:8282/
You should receive an HTTP response, typically
200 OKor another application-generated response.Verify that the application is not publicly bound:
ss -ltnp | grep 8282
Expected binding:
127.0.0.1:8282
If the container cannot write its database or logo directories, inspect ownership and permissions through the logs and fix the specific write-access problem. Do not apply
chmod 777to the installation. -
Configure DNS
Create an A record for your selected Wallos hostname.
For this example:
DNS setting Value Type A Hostname wallos.example.comValue Your VPS public IPv4 TTL 300 Check DNS resolution:
apt-get install -y dnsutils dig +short A wallos.example.com dig +short AAAA wallos.example.com
The A record should point to your server. If an AAAA record exists, it must also point to a server capable of serving this hostname over IPv6. Otherwise, remove the incorrect AAAA record before certificate issuance.
-
Configure the Nginx reverse proxy
We will terminate TLS at Nginx and forward requests internally to the Wallos container.
Create the virtual host:
cat > /etc/nginx/sites-available/wallos.conf <<'NGINX' server { listen 80; listen [::]:80; server_name wallos.example.com; server_tokens off; access_log /var/log/nginx/wallos-access.log; error_log /var/log/nginx/wallos-error.log warn; client_max_body_size 20m; location / { proxy_pass http://127.0.0.1:8282; proxy_http_version 1.1; proxy_set_header Host $host; 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 Connection ""; proxy_connect_timeout 10s; proxy_send_timeout 60s; proxy_read_timeout 60s; proxy_buffering on; proxy_redirect off; } } NGINXReplace
wallos.example.comwith your actual hostname:See Also: π How to Deploy XWiki on Debian VPS
sed -i \ 's/wallos\.example\.com/YOUR_ACTUAL_HOSTNAME/g' \ /etc/nginx/sites-available/wallos.conf
Alternatively, edit the file directly:
nano /etc/nginx/sites-available/wallos.conf
Enable the site:
ln -s \ /etc/nginx/sites-available/wallos.conf \ /etc/nginx/sites-enabled/wallos.conf
If this is a dedicated VPS with no other Nginx websites, you may remove the default site:
rm -f /etc/nginx/sites-enabled/default
Test and reload:
nginx -t systemctl reload nginx
Verify the HTTP reverse proxy using your real hostname:
curl -I http://YOUR_ACTUAL_HOSTNAME/
A successful response confirms Nginx can reach Wallos.
-
Configure the firewall
For a typical production VPS, permit inbound SSH, HTTP, and HTTPS. Do not expose Wallosβs internal port 8282.
Important: Before enabling UFW, allow the SSH port actually used by this VPS. An incorrect SSH rule can lock you out.
Inspect SSHβs listening ports:
ss -ltnp | grep sshd
If this server uses your customary SSH port 2211, configure:
ufw allow 2211/tcp
If it uses standard SSH port 22 instead:
ufw allow 22/tcp
Allow web traffic:
ufw allow 80/tcp ufw allow 443/tcp
Enable the firewall only after verifying SSH access is permitted:
ufw enable ufw status verbose
If the server uses IPv6, ensure UFWβs IPv6 handling is enabled.
Dockerβs published ports can bypass certain UFW policies. Our Compose file avoids publicly publishing port 8282 by binding explicitly to loopback. Docker documents the associated firewall behavior.
-
Enable HTTPS with Letβs Encrypt
Obtain and install the certificate:
certbot --nginx \ -d YOUR_ACTUAL_HOSTNAME \ --redirect \ --agree-tos \ -m admin@example.com \ --no-eff-email
Replace
admin@example.comwith your actual administrative email address.This command will obtain the certificate, configure Nginx for HTTPS, and redirect ordinary HTTP requests to HTTPS.
Verify:
nginx -t systemctl reload nginx
Check the certificate:
openssl s_client \ -connect YOUR_ACTUAL_HOSTNAME:443 \ -servername YOUR_ACTUAL_HOSTNAME \ </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Test the website:
curl -I https://YOUR_ACTUAL_HOSTNAME/
Also verify HTTP redirection:
See Also: π How to Deploy Cosmos Cloud on Ubuntu VPS
curl -I http://YOUR_ACTUAL_HOSTNAME/
The HTTP response should now redirect to HTTPS.
Add HTTP security headers
Once HTTPS works, open the SSL-enabled virtual host:
nano /etc/nginx/sites-available/wallos.conf
Within the HTTPS
serverblock, add:add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header X-Frame-Options "SAMEORIGIN" always;
These are conservative baseline headers. Avoid applying a restrictive Content Security Policy without testing Wallosβs scripts and styles.
Once you have verified HTTPS works for the hostname, you can optionally enable HSTS:
add_header Strict-Transport-Security \ "max-age=31536000" always;Use HSTS only when youβre prepared to maintain HTTPS continuously for the hostname.
Reload Nginx:
nginx -t && systemctl reload nginx
Verify automatic certificate renewal
systemctl list-timers --all | grep -i certbot
Then:
certbot renew --dry-run
Certbot supports scheduled renewal and provides this dry-run verification procedure.
-
Create the first Wallos account
Open your configured URL:
https://YOUR_ACTUAL_HOSTNAME/On a fresh installation, Wallos presents its initial registration screen. Create a strong, unique administrative password.
After signing in, review application settings, categories, currencies, and notification options.
Because Wallos may contain subscription, billing, or business financial details, I recommend restricting registration to approved users after creating your initial account. Review the versionβs administrative registration controls, if available.
For strictly internal use, consider putting Wallos behind a VPN or identity-aware access gateway rather than exposing its login page directly to the Internet.
-
Check Wallosβs scheduled jobs
Wallos uses scheduled tasks for payment-date maintenance, currency exchange updates, and notifications. Its Docker image incorporates a cron service, so do not immediately create duplicate host-level cron jobs.
Inspect processes in the running container:
docker exec wallos ps aux
Inspect available cron configuration:
docker exec wallos sh -c ' ls -la /etc/cron.d 2>/dev/null || true ls -la /etc/crontabs 2>/dev/null || true cat /etc/crontab 2>/dev/null || true '
Check container logs for scheduled-task failures:
docker logs wallos --tail=200
If you need to run a particular scheduled task manually for testing:
docker exec wallos php \ /var/www/html/endpoints/cronjobs/updatenextpayment.php
Only invoke a task manually after checking its intended behavior, particularly tasks that send notifications. This avoids accidentally generating duplicate messages.
-
Configure automated daily backups
Because Wallos stores subscription and application data in SQLite, database consistency matters.
Weβll use a short application stop during the backup to capture a consistent database, uploaded logos, and the Compose file together. This is simple and dependable, though it introduces a brief nightly service interruption.
See Also: Advanced Performance Tuning for WHM
Create the backup script
cat > /usr/local/sbin/wallos-backup.sh <<'BASH' #!/usr/bin/env bash set -Eeuo pipefail APP_DIR="/opt/wallos" BACKUP_DIR="/var/backups/wallos" DATE="$(date +%Y-%m-%d_%H%M%S)" ARCHIVE="${BACKUP_DIR}/wallos-${DATE}.tar.gz" mkdir -p "$BACKUP_DIR" chmod 700 "$BACKUP_DIR" cd "$APP_DIR" # Prevent concurrent backup executions. exec 9>/run/wallos-backup.lock flock -n 9 || { echo "Another backup is running." exit 1 } # Guarantee a restart if backup execution fails. STOPPED=0 restart_wallos() { if [ "$STOPPED" -eq 1 ]; then docker compose up -d fi } trap restart_wallos EXIT echo "Stopping Wallos for consistent backup..." docker compose stop STOPPED=1 echo "Creating archive..." tar -czf "$ARCHIVE" \ compose.yaml db logos chmod 600 "$ARCHIVE" echo "Starting Wallos..." docker compose up -d STOPPED=0 echo "Verifying archive..." tar -tzf "$ARCHIVE" >/dev/null echo "Deleting local archives older than 14 days..." find "$BACKUP_DIR" \ -maxdepth 1 \ -type f \ -name 'wallos-*.tar.gz' \ -mtime +13 \ -delete echo "Backup complete: $ARCHIVE" BASH chmod 700 /usr/local/sbin/wallos-backup.shThe script uses a lock to prevent overlapping executions and an exit trap to restart Wallos if backup execution fails.
Test it manually:
/usr/local/sbin/wallos-backup.sh
Verify the application returns to service:
docker compose -f /opt/wallos/compose.yaml ps
Check the backup:
ls -lh /var/backups/wallos/
And inspect its contents:
tar -tzf "$(ls -1t /var/backups/wallos/wallos-*.tar.gz | head -n 1)" | head -30
Schedule nightly backups
Create a systemd service:
cat > /etc/systemd/system/wallos-backup.service <<'EOF' [Unit] Description=Backup Wallos application data Requires=docker.service After=docker.service [Service] Type=oneshot ExecStart=/usr/local/sbin/wallos-backup.sh EOF
Create a timer:
cat > /etc/systemd/system/wallos-backup.timer <<'EOF' [Unit] Description=Nightly Wallos backup [Timer] OnCalendar=*-*-* 03:30:00 Persistent=true RandomizedDelaySec=5m [Install] WantedBy=timers.target EOF
Enable it:
systemctl daemon-reload systemctl enable --now wallos-backup.timer
Verify:
systemctl list-timers wallos-backup.timer
Review logs:
journalctl -u wallos-backup.service -n 50
This retains approximately 14 days of local backup archives. For production, also replicate the backup archives to a separate server or off-site object-storage service; backups on the same VPS cannot protect against complete VPS or storage failure.
-
Test database integrity
After youβve created your initial account, Wallos should have created its SQLite database.
Confirm:
See Also: Laravel vs Symfony: A Comprehensive Comparison of PHP Frameworks
ls -lah /opt/wallos/db/
Check the database:
sqlite3 /opt/wallos/db/wallos.db \ 'PRAGMA integrity_check;'
Expected output:
ok
If you see an error rather than
ok, investigate before upgrading or performing additional database operations.For additional validation, periodically test restoring a backup to a separate VPS or isolated Docker environment.
-
Update Wallos safely
I recommend manually controlled application updates rather than unattended container replacements. Image updates may contain database migrations or breaking changes, and maintaining a tested rollback path is important.
Create an update script:
cat > /usr/local/sbin/wallos-update.sh <<'BASH' #!/usr/bin/env bash set -Eeuo pipefail cd /opt/wallos echo "Backing up Wallos..." /usr/local/sbin/wallos-backup.sh echo "Pulling latest image..." docker compose pull echo "Recreating Wallos..." docker compose up -d echo "Current status:" docker compose ps echo "Recent logs:" docker compose logs --tail=50 wallos echo "HTTP check:" curl --fail --silent --show-error \ http://127.0.0.1:8282/ \ -o /dev/null echo "Update finished." BASH chmod 700 /usr/local/sbin/wallos-update.shWhen you wish to update:
/usr/local/sbin/wallos-update.sh
For tighter production change control, pin the application to a tested immutable image digest rather than tracking
latest. Before an upgrade, record the currently running image:docker inspect wallos \ --format '{{.Image}}'A backup permits data recovery, but a database migration may not be backward-compatible with the previous image. Rollback may therefore require restoring both the previous image and a pre-upgrade database backup.
-
Harden Ubuntu and Docker
Enable operating system security updates
Ensure unattended upgrades is installed:
apt-get install -y unattended-upgrades
Enable automatic security updates:
dpkg-reconfigure unattended-upgrades
Verify:
systemctl status apt-daily-upgrade.timer
Automatic application image updates remain separate from Ubuntuβs APT security updates.
Restrict Docker administration
Avoid adding ordinary users to the
dockergroup unless they are trusted system administrators. Membership provides privileges effectively equivalent to root access.Keep Docker administration limited to root or trusted privileged operators.
Verify network exposure
ss -ltnp
Ensure only the intended services listen on public interfaces.
Test Nginx:
nginx -t
Check firewall status:
ufw status numbered
Check Docker container privileges:
docker inspect wallos \ --format 'Privileged={{.HostConfig.Privileged}}'Expected:
Privileged=false
Check restart policy:
docker inspect wallos \ --format '{{.HostConfig.RestartPolicy.Name}}'Expected:
unless-stopped
Confirm data persistence:
docker inspect wallos \ --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}{{println}}{{end}}'You should see the database and logo bind mounts.
-
Troubleshooting
Symptom Diagnosis Wallos returns HTTP 502 curl -I http://127.0.0.1:8282/Container exits immediately docker logs wallos --tail=100Container reports unhealthy docker inspect wallos --format '{{json .State.Health}}'Port 8282 already occupied ss -ltnp \| grep 8282Database write failures ls -la /opt/wallos/dbNginx configuration errors nginx -tSSL renewal failures certbot renew --dry-runContainer wonβt restart journalctl -u docker -n 100Backup timer not running systemctl status wallos-backup.timerFor detailed live logs:
See Also: How to Install and Run Mastodon Server on AlmaLinux VPS
cd /opt/wallos docker compose logs -f --tail=100
Use
Ctrl+Cto exit the log viewer; the Wallos container remains running.
Final configuration summary
Once completed, your production deployment will have these characteristics:
| Feature | Configuration |
|---|---|
| Web access | https://YOUR_ACTUAL_HOSTNAME |
| Reverse proxy | Nginx with HTTPS |
| Container management | Docker Compose |
| Public container ports | None |
| Internal application port | 127.0.0.1:8282 |
| Application data | Persistent SQLite bind mount |
| Automatic container restart | Enabled |
| Docker log rotation | Enabled |
| TLS certificate renewal | Automatic |
| Daily backups | 03:30 local server time, with up to 5-minute delay |
| Local backup retention | Approximately 14 days |
| Wallos updates | Controlled, backup-first |
| Firewall | SSH, HTTP, HTTPS |
Official references
- Wallos β Official GitHub Repository
- Official Wallos Docker Compose Configuration
- Docker Engine Installation on Ubuntu
- Certbot Installation and Renewal Instructions
Recommended production practice: Treat the SQLite database as critical business data, preserve regular off-site backups, and verify successful restores. The combination of persistent storage, HTTPS, firewall restrictions, and backup-first updates provides a solid foundation for running Wallos on an Ubuntu VPS.
Conclusion
You now know how to deploy Wallos on Ubuntu VPS with Docker Compose.









