Install the Box IO Docker server on a Raspberry Pi
These steps install Box IO on a Raspberry Pi 4 or Raspberry Pi 5 with Raspberry Pi OS (64-bit). Docker runs two containers: boxio (the hub and the dashboard API) and nginx (HTTP and HTTPS). Arduino boards use HTTP port 5923. The other systems are under Docker Install: Ubuntu, CentOS, Red Hat, and BlueOnyx.
The Docker Hub image someone275/box_io-docker-server is built for PCs. A Pi uses a different CPU, so this machine builds Box IO itself. Use a Pi with at least 2 GB of RAM. The account you set in Raspberry Pi Imager is shown below as user. The Pi’s address on the LAN is x.x.x.x. Replace boxio.example.com with the dashboard name when you want a public certificate.
1. Write 64-bit Raspberry Pi OS
- In Raspberry Pi Imager, choose Raspberry Pi OS (64-bit). The 32-bit image cannot use these Docker packages.
- Open the imager settings. Set the hostname, username, and password. Turn on SSH. If the Pi uses Wi-Fi, set that network too.
- Write the card, put it in the Pi, and connect power. Wait until the Pi joins the LAN.
- Forward TCP
80,443, and5923from the router to this Pi when the dashboard must be reached from the internet. A Pi that only serves the home network can skip the forward. UDP is not required. - For a public name, add an A record for
boxio.example.com. The value is the router’s public WAN address. From a phone with Wi-Fi off,ping boxio.example.commust answer from that address.
2. Confirm the CPU and update
From a PC on the same LAN. raspberrypi.local works when the hostname is still the imager default. Otherwise use x.x.x.x.
ssh user@raspberrypi.local
Type the account password when SSH asks. Then check the CPU and update:
dpkg --print-architecture
sudo apt update
sudo apt upgrade -y
dpkg --print-architecture must print arm64. If it prints armhf, the card is 32-bit. Write it again with Raspberry Pi OS (64-bit) and start over.
3. Install Docker
Still signed in as user. Raspberry Pi OS is Debian, so this uses Docker’s Debian repository. The architecture in the source line follows the Pi, which is arm64.
sudo apt install -y ca-certificates curl git ufw
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a644 /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo usermod -aG docker user
The docker group applies on the next login. Sign out and back in:
exit
ssh user@raspberrypi.local
docker version
docker compose version
Both version commands should print a version. If docker says permission denied, the new login did not pick up the group yet.
4. Open the firewall
Allow SSH before you enable the firewall, or the next login can be locked out. Raspberry Pi OS does not turn this firewall on by itself.
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 5923/tcp
sudo ufw enable
sudo ufw status
5. Download Box IO onto the Pi
The earlier steps install Docker. They do not create a Box IO folder. This step downloads it. git is already installed.
cd ~
git clone https://github.com/Someone275/Box_IO-Docker-server.git
cd ~/Box_IO-Docker-server
curl -fsSL -o docker-compose.yml https://box-io.com/station/docker-compose.yml
curl -fsSL -o keep-up.sh https://box-io.com/station/keep-up.sh
curl -fsSL -o nginx/nginx.conf https://box-io.com/station/nginx.conf
chmod +x keep-up.sh
ls docker-compose.yml .env.example keep-up.sh nginx/nginx.conf
The clone creates ~/Box_IO-Docker-server. If git says that folder already exists, skip the clone and run the curl lines from inside it. ls must print those four names. The three curl lines are the current compose file and HTTPS proxy from this website.
6. Create the secret file
cd ~/Box_IO-Docker-server
cp .env.example .env
openssl rand -hex 48
Open .env and set JWT_SECRET to that hex string. One line, no quotes:
JWT_SECRET=paste_the_hex_here
Save the file. Never commit .env. Projects, users, and device keys are stored in the Docker volume boxio-data.
7. Build and start the containers
This compiles Box IO on the Pi. docker compose pull cannot download the app image for this CPU. Leave that command out.
cd ~/Box_IO-Docker-server
docker compose build
docker compose up -d
docker compose ps
docker compose logs -f --tail=50
The first build downloads the Node build tools and compiles the dashboard. Ctrl+C stops following the log. It does not stop the containers. boxio and nginx should both say Up. They restart with the Pi because the compose file uses unless-stopped. The nginx and certificate containers still come from Docker Hub. Box IO itself is the image this Pi just built.
If the build stops with Killed, the Pi ran out of memory. Raspberry Pi OS can use a larger swap file, then you run the build again:
sudo dphys-swapfile swapoff
sudo sed -i 's/^CONF_SWAPSIZE=.*/CONF_SWAPSIZE=2048/' /etc/dphys-swapfile
sudo dphys-swapfile setup
sudo dphys-swapfile swapon
free -h
docker compose build
docker compose up -d
From a PC on the same LAN, check the hub and the dashboard. The first certificate is self-signed, so the browser warning is expected until the next step.
http://x.x.x.x:5923/health
https://x.x.x.x
The health URL returns JSON with ok set to true. Port 443 is the dashboard. Arduino sketches keep using HTTP on port 5923.
8. Issue the HTTPS certificate
Skip this step when the Pi stays on the home network. Open https://x.x.x.x and accept the browser warning.
For a public name, Let’s Encrypt has to see it. On the Pi, both of these must print boxio-http-ok:
curl -sS http://127.0.0.1/http-ok
curl -sS http://x.x.x.x/http-ok
Then, from a phone with Wi-Fi off, open http://boxio.example.com/http-ok. It must show the same text.
When that public check works, request the certificate:
cd ~/Box_IO-Docker-server
chmod +x nginx/init-letsencrypt.sh nginx/ensure-certs.sh nginx/issue-cert-dns.sh
DOMAIN=boxio.example.com EMAIL=you@example.com ./nginx/init-letsencrypt.sh
Use an email address you can read. Let’s Encrypt sends expiry notices there. If the script times out during connect, port 80 is not reachable from the internet. Use the DNS method instead. It prints a TXT name and value. Create that record, wait until dig shows it, then press Enter in the script:
cd ~/Box_IO-Docker-server
EMAIL=you@example.com ./nginx/issue-cert-dns.sh
dig +short TXT _acme-challenge.boxio.example.com
From the phone on cellular, https://boxio.example.com should open with a normal padlock.
Renew the certificate automatically
The certificate from init-letsencrypt.sh lasts 90 days. Let’s Encrypt issues a replacement when fewer than 30 days remain. Nginx keeps serving the copied certificate until it is reloaded, so a nightly crontab runs nginx/renew-cert.sh. That script renews the certificate, copies it into place, and reloads nginx. A certificate from issue-cert-dns.sh is not renewed by this job. Run that script again before 90 days. Skip this when the Pi stays on the home network and keeps the first self-signed certificate.
cd ~/Box_IO-Docker-server
curl -fsSL -o nginx/renew-cert.sh https://box-io.com/station/renew-cert.sh
chmod +x nginx/renew-cert.sh
sh nginx/renew-cert.sh
(crontab -l 2>/dev/null | grep -v renew-cert.sh; echo '15 3 * * * cd /home/user/Box_IO-Docker-server && /home/user/Box_IO-Docker-server/nginx/renew-cert.sh >>/home/user/boxio-cert-renew.log 2>&1') | crontab -
crontab -l
15 3 * * * is 03:15 every night. Cron does not expand ~, so the command uses /home/user. Change user if the SSH account has another name. crontab -l lists the line. The log is /home/user/boxio-cert-renew.log. The account that owns the crontab must be allowed to run Docker.
9. Create the admin account
- Open
https://x.x.x.x, orhttps://boxio.example.comafter the certificate step. - Create the admin username and password. This is the first user, so the page asks for it.
- Sign in. Under device keys, generate a key. It starts with
bx_. Copy it into the sketch asBOXIO_AUTH. The sketch host isx.x.x.xand the port is5923. - Create a project and assign that device key.
- Turn on Edit, add widgets, set each virtual pin, and Save layout.
- Switch to Live before using buttons, sliders, and the input box.
- Optional: in Settings, save SMTP for email and Twilio for SMS. The email and SMS pages list those fields.
10. Install a license
Pin values, graphs, and /public/... stay empty until this Docker server has a license. Open License in the dashboard.
- If the Pi can reach box-io.com, sign in with the account from the license page. The server stores a token and installs the license you pick.
- If it cannot, download the license file from your account, upload it on the License page, paste the checkout code back on the website, then paste the confirmation code into Docker.
A year is $30. A trial is 30 days, once per account. Auto-renew charges $30 again each year. The web pin page lists each read address after the license is valid.
11. Update an existing Pi
git pull brings the newer program. The Pi compiles it again. The volume boxio-data stays, so users, device keys, and layouts stay. If git stops because docker-compose.yml or nginx/nginx.conf changed on this Pi, run git checkout -- docker-compose.yml nginx/nginx.conf and then git pull again before the curl lines.
cd ~/Box_IO-Docker-server
git pull
curl -fsSL -o docker-compose.yml https://box-io.com/station/docker-compose.yml
curl -fsSL -o keep-up.sh https://box-io.com/station/keep-up.sh
curl -fsSL -o nginx/nginx.conf https://box-io.com/station/nginx.conf
chmod +x keep-up.sh
docker compose build
docker compose up -d
docker compose ps
From ~/Box_IO-Docker-server, docker compose restart restarts the containers, and docker compose down stops them without deleting the volume. Do not use docker compose down -v. That deletes the saved dashboards. If the browser says 502 Bad Gateway or 504 Gateway Time-out after one page, run the curl lines again and then docker compose up -d --force-recreate.
12. Export and import this station
Use this to move the station you are running now onto another machine. Export writes one archive and starts the station again. On the next machine, import replaces the database, the license, the JWT secret, and the HTTPS certificate. Keep the archive private and delete it after the import.
cd ~/Box_IO-Docker-server
sh scripts/export-data.sh -o ~/boxio-export.tar.gz
Copy boxio-export.tar.gz to the next server, then from ~/Box_IO-Docker-server there:
sh scripts/import-data.sh ~/boxio-export.tar.gz
curl -sk https://127.0.0.1/api/health
rm -f ~/boxio-export.tar.gz
Sign in with the same admin account. Device keys and layouts are in the database.