Your finance, IT, and operations teams can lose a surprising amount of time answering simple questions: who has this laptop, when was it purchased, which employee signed for it, and is the warranty still active? Snipe-IT is a mature open-source asset management platform that gives teams a clean inventory workflow for laptops, phones, accessories, licenses, consumables, and audit history. This guide deploys Snipe-IT behind Caddy with MariaDB using Docker Compose on Ubuntu, with a layout designed for a small business or internal IT team that wants predictable upgrades, clean backups, and HTTPS without turning the asset tracker into a fragile weekend project.
The deployment pattern below mirrors the practical SysBrix Guides style: keep the application stack isolated, expose only Caddy to the public internet, store secrets in an environment file with restricted permissions, and verify each layer before inviting users. It is intentionally conservative. You can run it on a single VPS today, then move the database or file storage later if asset volume or compliance requirements grow.
Architecture and flow overview
The browser talks to Caddy on ports 80 and 443. Caddy terminates TLS and proxies traffic to the Snipe-IT container listening only on 127.0.0.1:8087. The Snipe-IT container connects to MariaDB over an internal Docker network, while uploads and imported asset photos live on a bind-mounted directory. This keeps the database private, makes backups straightforward, and avoids publishing the application container directly to the internet.
- Caddy: public HTTPS entry point, automatic certificates, compression, and security headers.
- Snipe-IT: asset management application, login, checkout history, labels, reports, and admin UI.
- MariaDB: relational database for assets, users, locations, licenses, and audit records.
- Persistent volumes: local directories for database state, uploaded files, and backup artifacts.
Prerequisites
- Ubuntu 22.04 or 24.04 server with a non-root sudo user.
- A DNS record such as
assets.example.compointing to the server. - Docker Engine, Docker Compose plugin, and Caddy installed.
- Outbound SMTP credentials for password resets and asset checkout emails.
- A backup target, even if the first version is a local directory synced elsewhere.
sudo apt update
sudo apt install -y ca-certificates curl gnupg ufw
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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 update && sudo apt install -y caddy
If the copy button is unavailable in your browser, select the command block and copy it manually.
Step-by-step deployment
1. Create the application directory
Keep Snipe-IT under /opt so service files, backups, and operational runbooks have a predictable location. Restrict permissions before you place database passwords or SMTP credentials in the directory.
sudo mkdir -p /opt/snipeit/data/{mariadb,uploads} /opt/snipeit/backups
sudo chown -R $USER:$USER /opt/snipeit
cd /opt/snipeit
umask 077
touch .env docker-compose.yml backup.sh
If the copy button is unavailable in your browser, select the command block and copy it manually.
2. Generate an application key and strong secrets
Snipe-IT requires a Laravel application key. You can generate random database passwords locally and then generate the app key after the first container image is available. Never reuse the MariaDB root password as the application password.
openssl rand -base64 32
openssl rand -base64 32
docker run --rm snipe/snipe-it:v7.1.15 php artisan key:generate --show
If the copy button is unavailable in your browser, select the command block and copy it manually.
3. Create the environment file
Replace the placeholder hostnames and secrets. The APP_URL value must match the final HTTPS URL; otherwise email links, redirects, and secure cookies can behave inconsistently after login.
APP_URL=https://assets.example.com
APP_TIMEZONE=UTC
APP_LOCALE=en-US
APP_KEY=base64:replace_with_output_from_key_generation
APP_DEBUG=false
MYSQL_DATABASE=snipeit
MYSQL_USER=snipeit
MYSQL_PASSWORD=replace_with_long_random_password
MYSQL_ROOT_PASSWORD=replace_with_different_long_random_password
DB_CONNECTION=mysql
DB_HOST=mariadb
DB_DATABASE=snipeit
DB_USERNAME=snipeit
DB_PASSWORD=replace_with_long_random_password
MAIL_MAILER=smtp
MAIL_HOST=smtp.example.com
MAIL_PORT=587
[email protected]
MAIL_PASSWORD=replace_with_smtp_secret
MAIL_ENCRYPTION=tls
[email protected]
MAIL_FROM_NAME="Asset Desk"
SESSION_SECURE_COOKIE=true
If the copy button is unavailable in your browser, select the command block and copy it manually.
4. Add Docker Compose
The application is bound to localhost only. That detail matters: Caddy can reach the service on the host, but random scanners cannot bypass the reverse proxy or hit the container directly.
services:
mariadb:
image: mariadb:11.4
container_name: snipeit-db
restart: unless-stopped
command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
volumes:
- ./data/mariadb:/var/lib/mysql
networks: [snipeit]
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 20s
timeout: 5s
retries: 10
snipeit:
image: snipe/snipe-it:v7.1.15
container_name: snipeit-app
restart: unless-stopped
depends_on:
mariadb:
condition: service_healthy
env_file: .env
volumes:
- ./data/uploads:/var/lib/snipeit
ports:
- "127.0.0.1:8087:80"
networks: [snipeit]
networks:
snipeit:
driver: bridge
If the copy button is unavailable in your browser, select the command block and copy it manually.
5. Start the stack and initialize Snipe-IT
Bring up MariaDB first through Compose, then allow the Snipe-IT container to run migrations. The first web request will present the setup wizard where you create the administrator account and configure company defaults.
cd /opt/snipeit
docker compose up -d
docker compose logs -f --tail=120 snipeit
# When logs are clean, open https://assets.example.com after Caddy is configured.
If the copy button is unavailable in your browser, select the command block and copy it manually.
6. Configure Caddy
Add a site block for the asset portal. If you already manage multiple applications, place this block in your existing Caddyfile and reload. The reverse proxy target matches the localhost port published by Compose.
assets.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8087
header {
X-Content-Type-Options nosniff
X-Frame-Options SAMEORIGIN
Referrer-Policy strict-origin-when-cross-origin
}
}
If the copy button is unavailable in your browser, select the command block and copy it manually.
sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
If the copy button is unavailable in your browser, select the command block and copy it manually.
Configuration and secrets handling best practices
For production use, treat Snipe-IT as a system of record. It may contain employee names, assigned devices, serial numbers, procurement notes, and license seats. Limit server access to administrators, keep .env readable only by the deployment user, and document who can export reports. If you use a password manager or secret platform, store the generated values there instead of only in the server file.
- Use SSO if your organization standardizes identity, or enforce strong local admin passwords.
- Configure SMTP before inviting users so password resets and checkout notices work.
- Back up both MariaDB and uploaded files; one without the other is an incomplete restore.
- Keep asset import templates under version control without including real employee data.
cd /opt/snipeit
chmod 600 .env
chmod 700 data backups
sudo chown -R $USER:$USER /opt/snipeit
sudo journalctl -u caddy --since "10 minutes ago" --no-pager
If the copy button is unavailable in your browser, select the command block and copy it manually.
Verification checklist
Validate the deployment before importing production data. A successful check confirms TLS, reverse proxy routing, database health, and application logs.
curl -I https://assets.example.com
curl -s https://assets.example.com/login | grep -i "snipe"
docker compose ps
docker compose logs --tail=80 snipeit
If the copy button is unavailable in your browser, select the command block and copy it manually.
- The HTTPS response should return
200or a setup/login redirect, not a Caddy gateway error. docker compose psshould show both containers healthy or running.- Snipe-IT logs should not repeat database connection, app key, or permission errors.
- Create a test asset, assign it to a test user, upload a small image, then export a report.
ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable
ss -ltnp | grep ':8087'
If the copy button is unavailable in your browser, select the command block and copy it manually.
Backups and recovery routine
Asset systems become operationally important faster than expected. Schedule a database dump and uploads archive, then copy the backup set to object storage or another host. Test at least one restore into a temporary directory before assuming the runbook works.
#!/usr/bin/env bash
set -euo pipefail
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
mkdir -p backups
/usr/bin/docker compose exec -T mariadb mariadb-dump \
-u"${MYSQL_USER}" -p"${MYSQL_PASSWORD}" "${MYSQL_DATABASE}" \
| gzip > "backups/snipeit-${STAMP}.sql.gz"
tar -czf "backups/snipeit-uploads-${STAMP}.tar.gz" data/uploads
find backups -type f -mtime +14 -delete
If the copy button is unavailable in your browser, select the command block and copy it manually.
chmod +x /opt/snipeit/backup.sh
cd /opt/snipeit
set -a; . ./.env; set +a
./backup.sh
ls -lh backups
If the copy button is unavailable in your browser, select the command block and copy it manually.
For cron, load the environment file explicitly so the backup script receives the database credentials. Add remote sync after the local archive succeeds, not before, so failed dumps are visible.
sudo tee /etc/cron.d/snipeit-backup >/dev/null <<'EOF'
15 2 * * * deploy cd /opt/snipeit && set -a && . ./.env && set +a && ./backup.sh >> backups/backup.log 2>&1
EOF
sudo chmod 644 /etc/cron.d/snipeit-backup
If the copy button is unavailable in your browser, select the command block and copy it manually.
Updates and maintenance
Patch during a maintenance window. Take a fresh backup, read the Snipe-IT release notes, pull images, and run migrations. Keep the previous backup until users confirm login, asset checkout, and imports still work.
docker compose pull
docker compose up -d
docker compose exec snipeit php artisan migrate --force
docker compose exec snipeit php artisan config:clear
docker compose ps
If the copy button is unavailable in your browser, select the command block and copy it manually.
Common issues and fixes
- 502 Bad Gateway: confirm the container publishes
127.0.0.1:8087:80and Caddy points to the same port. - Redirect loops or insecure cookies: set
APP_URLto the final HTTPS URL and keepSESSION_SECURE_COOKIE=true. - Database connection refused: verify MariaDB is healthy and that
DB_HOST=mariadb, not localhost. - Uploads fail: check ownership and free disk space under
/opt/snipeit/data/uploads. - Email does not send: test SMTP credentials, port, TLS mode, and sender address with your provider.
FAQ
Can I use PostgreSQL instead of MariaDB?
Snipe-IT is commonly deployed with MySQL or MariaDB. Use MariaDB unless you have a tested compatibility reason and a supported image configuration for another database.
Should Caddy run inside Docker too?
You can run Caddy in Docker, but a host-level Caddy service is simple for multi-app servers. It also makes certificate storage and firewall rules easier to reason about.
Where should asset photos and uploaded receipts live?
This guide stores them under /opt/snipeit/data/uploads. Back up that directory with the database, because the database references files that must exist after restore.
How do I prepare for employee offboarding?
Create status labels and checkout workflows before launch. Then require every device handoff, accessory return, and license removal to be recorded in Snipe-IT.
Can I import existing inventory from spreadsheets?
Yes. Clean the spreadsheet first, standardize serial numbers and locations, test a small import, then keep the original file as an audit artifact.
What monitoring should I add?
Monitor HTTPS availability, disk usage, backup freshness, and container restarts. For deeper checks, create a synthetic login or scheduled report export.
How often should I upgrade?
Review updates monthly, patch urgent security releases sooner, and always take a database plus uploads backup immediately before migrations.
Internal links
- Production Backups with Kopia, S3 Storage, Docker Compose, and Caddy
- Production Guide: Deploy Plane with Docker Compose + Caddy + PostgreSQL + Redis + MinIO on Ubuntu
- Production Guide: Deploy Penpot with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
- Contact SysBrix for deployment help
Talk to us
If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.