Skip to content

Installation

MrJSA edited this page Nov 2, 2026 · 5 revisions

This guide installs the shop on a Linux server (Ubuntu 22.04/24.04, Debian 12 or 13) with Docker. At the end you have:

  • the shop at https://shop.example.com
  • the admin dashboard at https://admin.example.com
  • automatic HTTPS certificates, database backups before every update, and one-click updates from the admin

Replace example.com with your own domain throughout.

Prefer Podman over Docker? See Installation with Podman (with Caddy as reverse proxy).

1. Requirements

Minimum
Server Linux VPS/root server, 2 vCPU, 2 GB RAM (building the Rust backend needs memory; add swap on smaller servers)
Disk 10 GB free
Domain Two (sub)domains, e.g. shop.example.com and admin.example.com
Ports 22 (SSH), 80 and 443 (HTTPS)

Small server? Add 2 GB swap:

sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

2. DNS

At your domain provider create two records pointing to the server's public IP:

shop.example.com    A    203.0.113.10
admin.example.com   A    203.0.113.10

If the server has IPv6, add an AAAA record for each of the two names as well. Hosting panels often show only the network (e.g. 2001:db8:4f2a:91c0::/64), which is not an address. The server's own address is somewhere in that network and can't be guessed (it may end in ::1 or in a long suffix like 2001:db8:4f2a:91c0:5c3e:a7ff:fe12:8b4d). Look it up on the server with ip -6 addr show scope global: the address after inet6, without /64. No inet6 line means no IPv6 yet, so skip the AAAA records.

A wrong AAAA record breaks HTTPS for every visitor with IPv6. Before saving, check from another machine with ping6 <address>.

Wait until ping shop.example.com shows your server IP.

3. Install Docker and git

sudo apt update && sudo apt install -y git curl
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER   # log out and back in afterwards
docker compose version          # must print v2.x

4. Download the shop

sudo mkdir -p /opt/rustwebshop && sudo chown $USER: /opt/rustwebshop
git clone https://github.com/MrJSA/RustCraft.git /opt/rustwebshop
cd /opt/rustwebshop
git checkout "$(git describe --tags --abbrev=0 2>/dev/null || echo master)"   # newest release

Why "rustwebshop" and not "RustCraft"? RustCraft used to be called rustwebshop. The old name stays as the folder name and as the stack name in docker-compose.yml (name: rustwebshop), so containers, networks and volumes are called rustwebshop-… / rustwebshop_… (e.g. rustwebshop_pgdata for the database). That is intended: renaming the stack would make existing installations start with new, empty volumes, i.e. without their database and uploads. Do not change the name in docker-compose.yml; the updater refuses to update an installation with local code changes.

5. Configure

Create .env, readable only by you, with a random database password and signed updates switched on:

cp .env.example .env
chmod 600 .env
sed -i -e "s|^DB_PASSWORD=.*|DB_PASSWORD=$(openssl rand -hex 24)|" \
       -e "s|^UPDATE_REQUIRE_SIGNED=.*|UPDATE_REQUIRE_SIGNED=true|" \
       -e "s|^NO_NEW_PRIVILEGES=.*|NO_NEW_PRIVILEGES=true|" .env
grep -E '^(DB_PASSWORD|UPDATE_REQUIRE_SIGNED|NO_NEW_PRIVILEGES)=' .env
nano .env

The grep must show a 48-character random password, UPDATE_REQUIRE_SIGNED=true and NO_NEW_PRIVILEGES=true.

Do this before the first start. The database takes its password on the very first docker compose up and keeps it. Changing it later also needs a command inside the database (see Troubleshooting). The backend warns in its log (SECURITY: the database password is a published default …) if the password from .env.example or a short one is used.

Setting Why
UPDATE_REQUIRE_SIGNED=true Updates are only installed when the release is signed by the RustCraft release key (see Updates)
NO_NEW_PRIVILEGES=true No program inside the containers can gain privileges through setuid binaries. Works with Docker; with Podman leave it false (see Installation with Podman)

Then, in nano, set at least:

SHOP_PUBLIC_URL=https://shop.example.com
ADMIN_PUBLIC_URL=https://admin.example.com

# Built-in HTTPS proxy (recommended unless the server already runs nginx/Apache/Traefik)
COMPOSE_PROFILES=proxy
SHOP_DOMAIN=shop.example.com
ADMIN_DOMAIN=admin.example.com
PUBLISH_ADDR=127.0.0.1

Any host names work, as long as shop and admin differ: the shop can live on the main domain (example.com) or a subdomain (shop.example.com), and the admin on any other (sub)domain, including one below the shop (admin.shop.example.com). Each needs its own DNS A/AAAA record pointing to this server.

Using your own web server instead? Leave COMPOSE_PROFILES empty, keep PUBLISH_ADDR=127.0.0.1 and see Own Reverse Proxy.

6. Start

docker compose up -d --build        # first build takes 5–15 minutes
docker compose ps                   # db and backend "healthy", the others "running"
docker compose logs backend | grep -i security   # must print nothing

docker compose ps also lists repo-move as finished: it is a one-time helper and that is correct.

Get the initial admin password (shown once):

docker compose logs backend | grep "Initial admin"

Open https://admin.example.com, log in as admin with that password and choose your own password (min. 12 characters).

7. Firewall

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw enable

Do not open 8080/4000/8081 — with PUBLISH_ADDR=127.0.0.1 they only listen locally anyway.

8. What the installation protects by default

  • The database is on its own internal network: only the backend can reach it, and it has no internet access.
  • Backend, storefront and admin run without root rights and with all unneeded capabilities removed. Log files are limited in size.
  • Updates are only installed when signed with the release key, are backed up first, and are rolled back automatically if the shop does not come back healthy (see Updates).
  • More in Security.

Next

Continue with First Steps, then switch on Backups.

Clone this wiki locally