Box IO

Self-hosted IoT for Arduino and ESP32.

Install the Box IO Docker server on CentOS

These steps install the prebuilt image someone275/box_io-docker-server from Docker Hub on CentOS Stream 9 or CentOS Stream 10. 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, Red Hat, Raspberry Pi, and BlueOnyx. A BlueOnyx panel already owns ports 80 and 443, so use that page there.

Replace boxio.example.com with the dashboard name, and user with the SSH account. Do not put the SSH password, the JWT secret, SMTP passwords, or Twilio tokens in git.

1. Point DNS and forward the ports

  1. Add an A record for boxio.example.com. The value is the router’s public WAN address.
  2. From a phone with Wi-Fi off, ping boxio.example.com must answer from that WAN address.
  3. Forward TCP 80, 443, and 5923 from the router to this CentOS machine. UDP is not required.

2. Sign in and update CentOS

ssh user@boxio.example.com
sudo dnf -y upgrade

3. Install Docker Engine

This installs Docker Engine and the Compose plugin from Docker’s CentOS repository. CentOS Stream uses dnf.

sudo dnf -y install dnf-plugins-core git
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo dnf -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker user

The docker group applies on the next login. Sign out and back in, then check both version commands:

exit
ssh user@boxio.example.com
docker version
docker compose version

4. Open firewalld

Leave SSH reachable. firewall-cmd adds the dashboard ports and the Arduino hub. If firewalld is not running, the host firewall still has to allow these ports.

sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=5923/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

SELinux can stay enforcing. The compose file labels the nginx files so the container can read them.

5. Download Box IO onto the server

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. docker compose pull downloads someone275/box_io-docker-server from Docker Hub. This machine does not compile Node. If pull still asks for ghcr.io, add BOXIO_IMAGE=someone275/box_io-docker-server:latest to .env.

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. Start the containers

cd ~/Box_IO-Docker-server
docker compose pull
docker compose up -d --no-build
docker compose ps
docker compose logs -f --tail=50

boxio and nginx should both say Up. The health URL on the LAN returns JSON with ok set to true. The first certificate is self-signed, so a browser warning is expected until the next step.

http://127.0.0.1:5923/health
https://boxio.example.com

8. Issue the HTTPS certificate

Let’s Encrypt has to see the public name. On the server, curl -sS http://127.0.0.1/http-ok must print boxio-http-ok. From a phone on cellular, http://boxio.example.com/http-ok must show the same text.

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

If that times out, port 80 is not reachable from the internet. Use the DNS method. It prints a TXT name and value. Create that record, wait until dig shows it, then press Enter:

cd ~/Box_IO-Docker-server
EMAIL=you@example.com ./nginx/issue-cert-dns.sh
dig +short TXT _acme-challenge.boxio.example.com

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.

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

  1. Open https://boxio.example.com and create the admin username and password.
  2. Generate a device key. It starts with bx_. Copy it into the sketch as BOXIO_AUTH.
  3. Create a project, add widgets, Save layout, then switch to Live.
  4. Open License and install a trial or a paid year before pin values will show.

10. Update

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
docker compose pull
docker compose up -d --no-build
docker compose ps

docker compose pull updates the image from Docker Hub. The volume boxio-data stays. From this directory, docker compose restart restarts the containers, and docker compose down stops them without deleting the volume.

11. 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.