Workload · Tracking your company's IT assets

Self-hosted Snipe-IT: your IT asset register on a server of your own

Running Snipe-IT yourself means the official Docker image plus a MariaDB database, one settings file to fill in, an admin account you create over a private connection, and HTTPS in front once that account exists. This guide does it on a dedicated server with Ubuntu 24.04, drawing on Snipe-IT's documentation and source, and flags what we added.

The short version
  1. Official snipe/snipe-it image, held at release v8.8.3
  2. Your admin account exists before the outside world can reach the app
  3. Email configured, so password resets and alerts work
  4. Nightly backup: database, attachments and your settings file
  5. Free software, AGPL-3.0

What you need before you start

Three things: a Linux server with Docker, a web address, and a mailbox Snipe-IT can send from. Snipe-IT is a PHP app on MySQL or MariaDB, but you never install PHP: the official image carries PHP 8.3 and Apache on Ubuntu 24.04 (its Dockerfile), and the compose file runs MariaDB 11.4.7 beside it. The docs give no processor, memory or disk figure; plan disk for attachments and backups.

You will haveWhere it lives
The Snipe-IT appa container only the server itself can reach, on 127.0.0.1:8090
The MariaDB databasea second container, with no port open to anyone
Attachments, keys and Snipe-IT's own backupsone Docker volume, mounted at /var/lib/snipeit
HTTPS for your teamCaddy, installed on the server, ports 80 and 443
Passwords and settings/srv/snipe-it/.env, readable by root only

Which server to rent for it

Your asset register knows who has which laptop and the licence keys you paid for, so give it a machine that is only yours. A dedicated server means the entire computer is yours: no other customer, root access, and an unmetered port.

Snipe-IT is light: a one-processor server from the everyday range suits most teams. A cartridge works too if it is an m510 or m710x; those, like every rack server and blade, give you a console outside the operating system, the screen and power button even when SSH is down. Other cartridges have power switching alone. If a firewall rule ever locks you out, that console gets you back.

Leave out the ARM X-Gene cartridge here. Its choice of systems stops at Ubuntu 16.04 and 18.04, and this guide needs 24.04.

You manage the server; we keep the hardware and the network running. Ready servers are handed over within 30 minutes of approval, once the payment checks are done; ready machines lists them. Prefer to choose every part? A build to order is ready 4 to 24 hours later when we hold the parts.

Up and running in an evening

  1. Choose Ubuntu 24.04 in the control panel. Then add Docker Engine with its Compose plugin, following the guide on Docker's own site; Snipe-IT's Docker page assumes you already have both.
  2. Point your team's address, say inventory.example.com, at the server in DNS: an A record, and also an AAAA record should the machine have an IPv6 address. Caddy requests its certificate the minute the site is loaded, which means DNS must already answer; getent ahosts inventory.example.com should show your server.
  3. As root, paste the block below. It does nothing if Docker Engine is older than 28 or /srv/snipe-it/.env already exists. Otherwise it turns on the firewall (IPv6 covered, SSH allowed), fetches the v8.8.3 compose file and sample settings, makes the settings file private before any secret goes in, replaces the sample changeme1234 passwords and adds a dc shortcut. No ok at the end? Stop.
ENGINE=$(docker version --format '{{.Server.Version}}' 2>/dev/null | cut -d. -f1)
if ! [ "$ENGINE" -ge 28 ] 2>/dev/null; then
  echo "Stopped: Docker Engine 28+ needed (found: ${ENGINE:-none})"
elif [ -e /srv/snipe-it/.env ]; then
  echo "Stopped: /srv/snipe-it/.env is already there"
else
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
ufw allow OpenSSH
ufw --force enable
apt-get update && apt-get install -y unzip
mkdir -m 700 /srv/snipe-it
TAG=v8.8.3
SRC=https://raw.githubusercontent.com/grokability/snipe-it/$TAG
curl -fsSL $SRC/docker-compose.yml -o /srv/snipe-it/docker-compose.yml
install -m 600 /dev/null /srv/snipe-it/.env
curl -fsSL $SRC/.env.docker -o /srv/snipe-it/.env
sed -i \
  -e "s/^APP_VERSION=.*/APP_VERSION=$TAG/" \
  -e 's/^APP_PORT=.*/APP_PORT=127.0.0.1:8090/' \
  -e 's|^APP_URL=.*|APP_URL=http://127.0.0.1:8090|' \
  -e "s/^DB_PASSWORD=.*/DB_PASSWORD=$(openssl rand -hex 24)/" \
  -e "s/^MYSQL_ROOT_PASSWORD=.*/MYSQL_ROOT_PASSWORD=$(openssl rand -hex 24)/" \
  /srv/snipe-it/.env
echo "alias dc='docker compose -f /srv/snipe-it/docker-compose.yml'" >> /root/.bashrc
grep -c changeme1234 /srv/snipe-it/.env | grep -qx 0 && grep -qx 'APP_PORT=127.0.0.1:8090' /srv/snipe-it/.env && echo ok
fi

Three .env lines are ours; the compose file stays as shipped. APP_VERSION pins a release, since Snipe-IT's Docker docs advise against latest. APP_PORT=127.0.0.1:8090 makes the compose file's ${APP_PORT:-8000}:80 a port only the server reaches, and APP_URL uses it until your admin exists. Docker publishes an address-less port on 0.0.0.0 and [::]; IPv4 packets for it go around ufw, IPv6 ones too if Docker handles IPv6 itself. From Engine 28, a 127.0.0.1 port is unreachable from outside. While sed runs, its arguments, passwords included, are visible to anyone listing processes; if others log in, type them in nano.

Email that actually arrives

Before the first start, open /srv/snipe-it/.env in nano and fill in the outgoing mail section from your provider: the server in MAIL_HOST and MAIL_PORT (587 in the docs' example), the login in MAIL_USERNAME and MAIL_PASSWORD, the sender in MAIL_FROM_NAME plus MAIL_FROM_ADDR, and a reply-to in MAIL_REPLYTO_ADDR. Set APP_TIMEZONE to your own zone too.

Skip it and, per the configuration page, password resets, warranty and licence expiry warnings, and asset acceptance or EULA requests all stop working; an empty from address can cause 500 errors. Alerts need no cron job: the image's supervisord runs Laravel's scheduler every minute (docker/supervisord.conf); recipients are set in Admin > Settings.

Now the key and the first start, from a new root shell so the dc shortcut exists:

  1. grep -q 3ilviXqB9u6 /srv/snipe-it/.env && NEWKEY=$(docker compose -f /srv/snipe-it/docker-compose.yml run --rm -T app php artisan key:generate --show) && sed -i "s|^APP_KEY=.*|APP_KEY=$NEWKEY|" /srv/snipe-it/.env makes a key only while the sample one is still there. The key command is the docs'; -T is ours, keeping stray terminal characters out. It is also MariaDB's first start, the moment its users get made from .env, hence the passwords went in first.
  2. Start only once the key is a fresh one: grep -Eqx 'APP_KEY=base64:[A-Za-z0-9+/]{43}=' /srv/snipe-it/.env && ! grep -q 3ilviXqB9u6 /srv/snipe-it/.env && dc up -d && dc port app 80. It should end with 127.0.0.1:8090.
  3. All of this setup runs once only. Never make a new APP_KEY for an install in use; store the one you have in your password manager, as the configuration page asks, since Snipe-IT encrypts stored data with it.

Your admin account, made in private

Snipe-IT's first account is made on a browser setup page, and that person becomes a superuser (SetupController.php), so do it while nothing outside can reach the app. From your computer, logging in as yourself, run ssh -N -L 8090:127.0.0.1:8090 you@server.example.net. The very first time you connect, SSH asks you to confirm the server's host key fingerprint (check it, then answer yes), and if you log in with a password it asks for that as well. Keep local port 8090: as we read AppServiceProvider.php, every link Snipe-IT draws begins with APP_URL.

  1. Visit http://127.0.0.1:8090. You arrive at the pre-flight check, which tests the database, makes sure the address you typed matches APP_URL, and confirms Snipe-IT can write to its storage folders.
  2. A warning about the .env file is normal through the tunnel: SetupController.php counts a failed fetch of APP_URL/.env, made within the container, as exposed, and nothing in there answers on 8090. You check it properly once HTTPS works.
  3. Use the pre-flight's mail test.
  4. Let it build the tables, then enter the site name and your own details as the first user. There is a box to email the login to yourself; we suggest leaving it unticked, so the password never sits in an inbox.

Putting it on its own web address

With the admin made, move Snipe-IT to its real address and tell it a proxy sits in front. Open /srv/snipe-it/.env in nano and change these:

  • APP_URL becomes https://inventory.example.com.
  • APP_TRUSTED_PROXIES gets the value REMOTE_ADDR, and in the next line SECURE_COOKIES is set to true: the pair the Docker page gives for an HTTPS proxy.
  • Add a line APP_TRUSTED_HEADERS= followed by HEADER_X_FORWARDED_PROTO, HEADER_X_FORWARDED_FOR and HEADER_X_FORWARDED_HOST, joined by commas without spaces. The docs do not mention it yet; v8.8.3's config/trustedproxy.php notes that leaving it unset means none of the forwarded headers are trusted; that Snipe-IT would then record the proxy's address for every visitor is our reading.

Trusting whichever address connects is safe only because the app container's web port can be reached solely through 127.0.0.1:8090, which Engine 28 and later keep to the server itself (our reasoning). Then run dc up -d; a plain restart would keep the old environment (Docker page).

Next, HTTPS with Caddy, packaged in Ubuntu 24.04. First ufw allow 80,443/tcp; second, apt-get install -y caddy. Caddy runs on the server itself, so without that rule visitors cannot reach it over either IPv4 or IPv6. Write its config with printf '%s\n' 'inventory.example.com {' ' reverse_proxy 127.0.0.1:8090' '}' > /etc/caddy/Caddyfile and apply it with systemctl reload caddy. Caddy gets its own certificate from Let's Encrypt and passes the X-Forwarded headers along (Caddy docs). Last, the first line of curl -sI https://inventory.example.com/.env should not say 200.

Keeping a copy of everything

Snipe-IT can back itself up, from Admin > Backups or with php artisan snipeit:backup: a zip holding an SQL dump plus all uploaded images and attachments, and its scheduler makes one every week (app/Console/Kernel.php). Those zips stay in the Docker volume, so a lost disk takes them too, and as we read config/backup.php and the Dockerfile, the .env inside is the image's placeholder from docker/docker.env; your key and passwords are stored only in /srv/snipe-it/.env. This script copies a fresh zip out, checks it and saves your settings beside it.

# /srv/snipe-it/nightly.sh -- run as root with: bash /srv/snipe-it/nightly.sh
set -euo pipefail
umask 077
DC="docker compose -f /srv/snipe-it/docker-compose.yml"
OUT=/var/backups/snipe-it
NAME=daily-$(date +%F-%H%M)
mkdir -p -m 700 "$OUT"
$DC exec -T -u docker app php artisan snipeit:backup --filename="$NAME"
$DC cp "app:/var/lib/snipeit/dumps/$NAME.zip" "$OUT/$NAME.zip"
unzip -Z1 "$OUT/$NAME.zip" | grep '\.sql$' > /dev/null
unzip -tq "$OUT/$NAME.zip" > /dev/null
tar -czf "$OUT/$NAME-env.tar.gz" -C /srv/snipe-it .env docker-compose.yml
find "$OUT" -name 'daily-*' -mtime +30 -delete

pipefail fails the run on any broken step, and the unzip checks refuse a damaged zip or one without a database dump. Thirty days on the server is our pick. For a nightly run: mkdir -p -m 700 /var/log/snipe-it, then printf '40 2 * * * root umask 077; bash /srv/snipe-it/nightly.sh > /var/log/snipe-it/nightly.log 2>&1\n' > /etc/cron.d/snipe-it-nightly.

Then get the backups off this server: copy /var/backups/snipe-it to a second machine of yours with rsync -a, never --delete (with it, a folder emptied here empties your safe copy too), and prune there on its own schedule. LDAP client certificates in /var/lib/snipeit/keys are not in the zip; save them separately.

Getting it back after a mistake

php artisan snipeit:restore puts back the database and files from one of its zips, leaves .env alone, and signs everyone out (backup page). Save this script and run it as root (sudo -i first) with the zip you want; it keeps a checked copy of today's state first and stops before touching anything if that copy fails.

# /srv/snipe-it/restore.sh -- run as root with:
#   bash /srv/snipe-it/restore.sh /var/backups/snipe-it/daily-STAMP.zip
set -euo pipefail
umask 077
DC="docker compose -f /srv/snipe-it/docker-compose.yml"
PICK=${1:?give the backup zip as the argument}
unzip -tq "$PICK" > /dev/null
# 1. Today's state first, checked, kept in /root (your way back)
SAFE=pre-restore-$(date +%F-%H%M%S)
$DC exec -T -u docker app php artisan snipeit:backup --filename="$SAFE"
$DC cp "app:/var/lib/snipeit/dumps/$SAFE.zip" "/root/$SAFE.zip"
unzip -tq "/root/$SAFE.zip" > /dev/null
echo "safety copy ok: /root/$SAFE.zip"
# 2. The restore. --force answers its only question, which reads:
#    Are you sure you wish to restore from the given backup file?
#    This can lead to MASSIVE DATA LOSS!
$DC cp "$PICK" app:/tmp/rollback.zip
$DC exec -T app chown docker /tmp/rollback.zip
$DC exec -T -u docker app php artisan snipeit:restore /tmp/rollback.zip --force
$DC exec -T app rm /tmp/rollback.zip
echo "restored from $PICK"

Then look over some recent check-outs. Regret it? Rerun the script with the /root/pre-restore-... file it printed. Rebuilding on a fresh machine? Extract the matching -env.tar.gz into /srv/snipe-it before anything starts there, because MariaDB reads its users from .env on its very first start only and your records need the original APP_KEY; then bring the stack up and run the script.

Staying up to date, and what the licence asks

Run nightly.sh before any upgrade; the upgrade page wants a database and .env backup first. Its upgrade.php is built for installs made with git; anywhere else it can only back up and clear caches. With Docker the new image is the new code: change the release in APP_VERSION, then dc pull followed by dc up -d. The start script, docker/startup.sh, applies migrations (php artisan migrate --force) each time it boots.

The licence is AGPL-3.0, the Affero version of the GPL (LICENSE in the repository), and it is copyleft. Here is our general reading, and it is not legal advice. Using it as shipped inside your company adds no obligation. Hand a copy to someone else, modified or as shipped, and the source code and licence must travel with it. Modify it and let others use your version across a network, and they are entitled to its source.

Questions people ask

Is Docker the only way to install it on Ubuntu?
No. Snipe-IT's preferred route is a git clone with PHP, a web server and the database on the host, where you create the database yourself and upgrade with upgrade.php. An install.sh also exists, for a clean Ubuntu, Debian or CentOS machine with no other sites.
Will my records survive docker compose down?
Yes, the volumes stay. Only docker compose down -v deletes them, and the Docker page warns it does so without asking.
How quickly can I start?
A ready server is handed over within 30 minutes of approval; the payment and identity checks before approval are the part without a fixed time. A custom build needs 4 to 24 hours when we hold the parts.

Give your asset register a server of its own

Pick a one-processor server, or a cartridge that has its own console, then put Ubuntu 24.04 on it and follow this guide.

Get a server for Snipe-IT

Read next