Box IO

Self-hosted IoT for Arduino and ESP32.

Install the Box IO Docker server on Linux

These steps install the Box IO station on Ubuntu 24.04. Docker runs two containers: boxio (the hub and the dashboard API) and nginx (HTTP and HTTPS). This station’s dashboard is http://localhost. Arduino boards use http://localhost:5923.

The Ubuntu machine on the LAN is x.x.x.x. The SSH user in the commands below is user. Use the address and account on your own computer. Do not put the SSH password, the JWT secret, SMTP passwords, or Twilio tokens in git.

1. Point DNS and forward the ports

Do this before the first boot so the public name can reach the machine.

  1. In DNS for your domain, add an A record for localhost. The value is the router’s public WAN address, not x.x.x.x. From a PC on that network, a “what is my IP” page shows the WAN address.
  2. On a phone with Wi-Fi off, ping localhost must answer from that WAN address.
  3. Forward these TCP ports from the router to x.x.x.x. UDP is not required.
    • 80 to port 80 (HTTP, and the Let’s Encrypt check)
    • 443 to port 443 (the dashboard)
    • 5923 to port 5923 (Arduino and ESP32)

2. Sign in and update Ubuntu

From a PC on the same LAN:

ssh user@x.x.x.x

Type the account password when SSH asks. Then update the OS:

sudo apt update
sudo apt upgrade -y

3. Install Docker

Still signed in as user. This installs Docker Engine and the Compose plugin from Docker’s own Ubuntu repository.

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/ubuntu/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/ubuntu $(. /etc/os-release && echo ${UBUNTU_CODENAME:-$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@x.x.x.x
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.

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. Copy Box IO onto the server

cd ~
git clone https://github.com/Someone275/Box_io.git box_io
cd ~/box_io/server

If clone fails, download the ZIP from GitHub, copy it to the server, unzip it, and rename the folder to box_io. Then cd ~/box_io/server.

6. Create the secret file

cd ~/box_io/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. The compose file reads this secret when the container starts. Projects, users, and device keys are stored in the Docker volume boxio-data, not in the git folder.

7. Start the containers

cd ~/box_io/server
docker compose up --build -d
docker compose ps
docker compose logs -f --tail=50

Wait until the log shows the device hub on port 5923 and the web API on port 3847. Ctrl+C stops following the log. It does not stop the containers. boxio and nginx should both say Up. They restart with the machine because the compose file uses unless-stopped.

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
http://localhost

The health URL returns JSON with ok set to true. Port 80 redirects to HTTPS. Port 443 is the dashboard. Arduino sketches keep using HTTP on port 5923.

8. Issue the HTTPS certificate

Let’s Encrypt has to see the public name. On the server, 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://localhost/http-ok. It must show the same text. A test from the LAN is not enough.

When that public check works, request the certificate:

cd ~/box_io/server
chmod +x nginx/init-letsencrypt.sh nginx/ensure-certs.sh nginx/issue-cert-dns.sh
DOMAIN=localhost 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/server
EMAIL=you@example.com ./nginx/issue-cert-dns.sh
dig +short TXT _acme-challenge.localhost

From the phone on cellular, http://localhost should open with a normal padlock. The DNS certificate is not renewed by the certbot loop. Run issue-cert-dns.sh again before 90 days.

9. Create the admin account

  1. Open http://localhost.
  2. Create the admin username and password. This is the first user, so the page asks for it.
  3. Sign in. Under device keys, generate a key. It starts with bx_. Copy it into the sketch as BOXIO_AUTH. Do not commit that key.
  4. Create a project and assign that device key.
  5. Turn on Edit, add widgets, set each virtual pin, and Save layout.
  6. Switch to Live before using buttons, sliders, and the input box.
  7. Optional: in Settings, save SMTP for email and Twilio for SMS. The email and SMS pages list those fields.

10. Update an existing station

Run this on the Ubuntu machine. git pull does not wipe boxio-data, so users, device keys, and layouts stay.

ssh user@x.x.x.x
cd ~/box_io
git checkout main
git pull origin main
cd server
docker compose up --build -d
docker compose ps

Hard-refresh the dashboard after both containers are Up. Sketches keep the same host, port 5923, and device key. From ~/box_io/server, docker compose restart restarts the containers, and docker compose down stops them without deleting the volume. docker compose up --build -d starts them again.

If the dashboard does not come back:

cd ~/box_io/server
docker compose logs -f --tail=80 boxio