# Docker and one-click apps

> Source: https://www.allsweb.com/sixpanel/docs/docker-and-apps
> Markdown for agents: https://www.allsweb.com/sixpanel/docs/docker-and-apps.md
> Publisher: AllsWeb (www.allsweb.com)

Part of: SixPanel documentation

**What this page is for:** run an app that ships as Docker containers — your own compose
file, or one of the one-click apps: **Supabase** (Postgres with sign-in, file storage,
realtime and the Studio dashboard) and **BillionMail** (email marketing with its own mail
server) — as a website of this server, behind its own domain with HTTPS.

**The rule that does not move:** SixPanel, your 6amMart projects and every other website
keep running directly on the server, never in a container. Docker is an **optional
component**: nothing of it is on the server until you press **Install**, and it is used
only by Docker apps, the one-click apps and the MongoDB and SQL Server engines.

**You need**

- Ubuntu 26.04, Ubuntu 24.04 or Debian 13, on an amd64 or arm64 server. On any other
  system the install is refused and says why.
- For Docker itself: 1 GB of memory and 4 GB free on the disk. Each app needs more — the
  app's card says how much before anything is made.
- On Ubuntu, the **universe** part of Ubuntu's archive, where its Docker packages live. If
  your server does not have it, the install says so and gives the one command that adds it
  (`sudo add-apt-repository universe`).
- The owner's login to install or remove Docker, change an app's environment or compose
  file, show a secret, clean up or restart Docker. A temporary login can start, stop and
  restart apps, change their settings, and reads their environment with every secret
  hidden.

---

## Install Docker

**Software → Docker → Install.** It takes a minute or two.

- **Where it comes from:** your system's own packages — `docker.io` and Docker Compose
  (`docker-compose-v2` on Ubuntu, `docker-compose` on Debian 13), and `docker-buildx`
  where the system has it. Nothing is added from any other source.
- **What SixPanel sets** (in `/etc/docker/daemon.json`):
  - a port a container publishes opens on **127.0.0.1 only** unless a file names another
    address — Docker writes its own firewall rules ahead of every other one, so a port
    open on every address would be reachable from the internet past Cloudflare and the
    firewall;
  - container logs are **capped** (10 MB, three files per container), so a chatty app
    cannot fill the disk;
  - containers **keep running while Docker restarts** (live-restore);
  - containers of different apps **cannot talk to each other** on Docker's default network;
  - Docker's own networks come from a private range that overlaps none of your server's
    networks.
  Settings you added to that file yourself are kept as they are.
- **Nothing outside the server reaches a container by its own address.** Docker before
  28.0 lets a machine on your server's network that routes Docker's private range through
  it reach a container directly, past the 127.0.0.1 its ports are published on — MongoDB,
  SQL Server and every app's database included. SixPanel adds a rule to Docker's own
  `DOCKER-USER` chain that drops such a connection (a port published on a public address
  still works), and a drop-in on `docker.service` puts it back each time Docker starts.
  **Health → Containers reached only where they are published** fails when it is gone. If
  your `daemon.json` says `"allow-direct-routing": true` (Docker's own setting for this),
  that choice is kept, and Health says what it means.
- **Nobody is added to the `docker` group.** Membership of that group is the same as root.
- **Docker was already installed?** SixPanel uses it as it is and writes its settings. If
  it is running containers SixPanel did not start, it is **not** restarted under them: the
  page says the new settings apply at the next restart, and **Restart Docker** does that
  when you choose. SixPanel never removes a Docker it did not install.
- **Remove:** refused while a Docker app or an engine still uses Docker — delete those
  first. Images and volumes stay on the disk unless you choose to delete them too.

## The Docker page

**Software → Docker** shows the engine (running or not, its version, the Compose
version, its settings, disk use) and has these tabs:

- **Containers** — every container with its state, image, ports, CPU, memory and who it
  belongs to (a Docker app, an engine, or nobody). Start, stop, restart, read its logs
  (**Refresh**, and how many lines).
  **Remove** is offered only for a container that belongs to nobody: an app's container is
  managed on the app's own page, an engine's on its Software card.
- **Images** — each image, its size and which containers use it. **Pull an image** downloads
  one by its name (like `nginx:1.29-alpine`); **Update** pulls an image's name again. A
  container made from the older copy keeps running it until it is made again — a Docker app's
  **Update images** on its page does that. **Remove** is offered only for an image no
  container uses, running or stopped.
- **Volumes**, **Networks** — what Docker holds, and whose each one is.
- **Clean up** — first **See what it would remove**: stopped containers that belong to
  nobody, images no container uses, networks nobody uses, anonymous volumes and the build
  cache. **Never a named volume** (that is somebody's data) and never anything of a Docker
  app or an engine. **Remove these** removes exactly that list; if anything changed since
  you read it, nothing is removed and you are asked to look again.
- **One-click apps** — Supabase and BillionMail, each saying whether it can run on this
  server now and why not.

---

## Docker apps

A Docker app is a **website** whose app runs in containers. **New → Website → Docker app**,
then pick what to run:

- **Sample app** — a tiny web server that shows the request it received, to see a Docker
  app work end to end. 64 MB at most.
- **Your own compose file** — paste the app's `docker-compose.yml` (and, if it reads
  variables, its `.env`), and name the **service** the domain goes to and the **container
  port** it listens on.
- **Supabase** and **BillionMail** — below.

**Where it lives.** Its folder is `/var/www/_sites/<name>/compose/`, readable by root only:
the compose file, what it mounts, and its `.env` (its secrets, root 0600). Its containers
are named `sp-<name>-<service>-1`.

**How it is reached.** The website's domain is served over HTTPS like every website, and
the web server passes each request to the app on `127.0.0.1:<port>` — one port SixPanel
gives the app (between 7100 and 7999). Every other port the app publishes opens on this
server only, so a database inside an app is never open to the internet. (BillionMail's
mail ports are the one exception — they are the point of a mail server.)

**Its memory ceiling.** Every container of the app runs inside one limit you set (the
whole app together, not each container), so an app can never take the memory your shop
and other websites need. Change it on the app's **Settings**.

### What a compose file may not do

Before anything runs, SixPanel asks Docker Compose for the file exactly as Compose reads it
(with its `.env`), checks that, and then runs **that checked version** — so what was checked
is what runs. A file is refused, with a sentence that says what to write instead, when it
asks for:

- a **privileged** container, the server's own **network**, processes, shared memory or
  host name (`network_mode: host`, `pid: host` …), the server's **devices** or GPUs, another
  container runtime, or a security profile switched off (`seccomp=unconfined` …);
- an added capability beyond the few that only touch the container itself (`SYS_NICE`,
  `IPC_LOCK`, `SYS_RESOURCE`, and the ones Docker gives anyway);
- a **folder of the server** mounted into a container — a mount must come from inside the
  app's own folder. That rules out the **Docker socket**, which is root on the server;
- the app's own `.env` mounted into a container;
- a **network or volume of another app**: `external: true`, or a `name:` written by hand,
  or volume driver options (a "local" volume with `o=bind` is any folder of the server);
- `include:`, `extends` from another file, or an `env_file` outside the app's folder;
- a **port open on every address**: write `127.0.0.1:8080:80` instead of `8080:80`, or
  switch on **Publish ports the file gives no address on this server only** in Settings.

What every container is given: capped logs, restart unless you stopped it, a process limit
and the app's memory ceiling. A fixed `container_name` is replaced by a name of the app's
own — and kept as a network alias, so other services still reach it by that name.

### Its page

- **App** — each service with its state, health, CPU and memory; **Start**, **Stop**,
  **Restart**, and **Update images** (downloads newer images for the tags the compose file
  names, then starts the app on them). For a one-click app, the addresses and keys it
  needs, each secret behind **Show**.
- **Environment** — the `.env`. Secrets show as `****` (a password written inside an
  address, like `DATABASE_URL`, too); the owner's **Show** reads one value and the Activity
  log records that it was shown. **Save and restart** changes the keys you edited and keeps
  every other line as it was.
- **Compose file** — the file itself. **Check** says what would stop it from running here
  without changing anything; **Save and start** checks it again, saves it and starts the
  app on it. A temporary login and Tia read it with values beside secret names as `****`.
- **Logs** — what its containers printed, all of them or one service. The app's own
  secrets are taken out.
- **Settings** — the memory ceiling, the largest upload the web server passes on, the
  service and port the domain goes to, and publishing ports on 127.0.0.1.

A Docker app has no Deploys, Database, PHP, Cron jobs, Files or Terminal page: its
compose file is what runs.

### Domain, backups and delete

- **Domain & SSL** works as for every website. Supabase's own addresses (its public URL
  and the address its sign-in emails link to) follow the domain on their own.
- **Backups** hold the app's folder (compose file, `.env`, what it mounts), its named
  volumes, and its **database** — for Supabase and BillionMail, their Postgres is copied
  while it is **paused** for a moment, so the copy is one Postgres can start from. A restore
  stops the app, puts its files, volumes and database back, and starts it again — even
  when a step failed half way.
- **Delete** stops the app and removes its containers, networks and volumes (its data).
  Docker must be running for that; the images stay until a **Clean up**.

---

## Supabase

**Needs** a server with **4 GB of memory** (a "4 GB" server reports about 3,800 MB; below
3,500 MB it is refused with the number), about 1.8 GB free to start, and 8 GB free on the
disk.

- **What is installed:** Supabase's own self-hosting files at one fixed version
  (self-hosted 0.8.3, 16 Oct 2026), each file checked against its fingerprint when it is
  downloaded — a file that differs installs nothing.
- **Every secret is generated for you:** the JWT secret, the **anon** and **service_role**
  keys signed with it, the database and Studio passwords and the encryption keys.
- **Studio** (the dashboard) is at `https://<your domain>/`, behind a sign-in: user
  `supabase`, the password under **Show** on the App tab.
- **In your app:** the API URL is `https://<your domain>`; the **anon** key is for browser
  code, the **service_role** key for server code only — never in a browser.
- **Postgres** is reachable on this server only — session mode and transaction mode, each
  on its own `127.0.0.1` port. The connection strings are on the App tab.
- **Sign-up and password emails:** when SixPanel Email is installed, Supabase sends through
  it — set `SMTP_USER` and `SMTP_PASS` (a mailbox from the Email page) on the Environment
  tab. Without it, set `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER` and `SMTP_PASS` to any mail
  service.
- **Update images** keeps it current within the version it was made from. Moving to a newer
  Supabase release is not done by SixPanel yet.

### Connect a website to Supabase

A Node.js, Python, Java or PHP website gets its Supabase values without typing them: open the
website's **Environment** page, card **Supabase**.

- **Use a Supabase on this server** (the server's owner): pick the Supabase app. The panel writes
  `NEXT_PUBLIC_SUPABASE_URL`, `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY`, `SUPABASE_SERVICE_ROLE_KEY` and
  `SUPABASE_DB_URL` (the database on `127.0.0.1`, for migrations) — or the names you type instead.
  The values are read from the Supabase app every time the website's environment is written, so
  when that app gets a new key or a domain, the website gets it too and restarts — or is built
  again, when a `NEXT_PUBLIC_` value changed, because those are put into the pages at build time.
  The website's address is added to the Supabase's **SITE_URL** (or its list of redirect addresses)
  so sign-in links come back to it. Until the Supabase app has a domain, its URL is this server's
  own address: server code reaches it, a visitor's browser does not.
- **Use Supabase Cloud** (the owner, or the account whose website it is): paste the project URL,
  the publishable key and the secret (service_role) key, and optionally the database connection
  string (**Connect → Session pooler**). Before anything is saved the panel asks Supabase with
  them: its Auth must answer with the publishable key, and the secret key must be allowed to list
  users, which only that project's service_role key is. A key of another project, an expired one,
  the two keys swapped, or a connection string still saying `[YOUR-PASSWORD]` are refused.
- **Keys are never shown** — every key is `****` on the card, and none is written into panel's
  settings file, a job log, Activity or Tia's answers. A Cloud project's keys are kept in one file
  only root can read.
- The service_role key and the database address never take a name starting `NEXT_PUBLIC_`,
  `VITE_` or another prefix that reaches the browser.
- **The website's Database page shows its Supabase** — its size and tables, **Manage data**
  in the Database manager, and a link to Studio or the Cloud dashboard. A website can also be
  made with Supabase from the start: the **Create** dialog's **Database** choice has
  **Supabase (this server)** and **Supabase Cloud**.
- **Disconnect** stops setting those variables. Nothing in Supabase is deleted.
- `sixpanel.json` can ask for the same: `"supabase": { "use": "<the Supabase app>" }`
  (see [sixpanel.json](https://www.allsweb.com/sixpanel/docs/sixpanel-json)).

### Move from Supabase Cloud to this server

On the same card, **Move from Supabase Cloud to this server** copies a Cloud project into a
**new** Supabase app here (a copy never writes over a Supabase that already has tables or users):

- your schemas, their tables and data, views and functions;
- the signed-up **users** and their sign-ins (Supabase's `auth` data), and the storage **buckets**;
- the **Storage files**, when the website is connected to that Cloud project (its secret key reads
  them) — a file over 50 MB is listed, not copied.

The database is put in as **one transaction**: if anything fails, the Supabase here is left exactly
as it was. Then connect the website to the new Supabase on the same card.

**What does not move** — do these by hand:

- Edge Functions (deploy them again into the app's `volumes/functions` folder);
- the project's secrets (Edge Function secrets, Vault values encrypted with Cloud's key);
- pg_cron jobs and database webhooks;
- Auth settings: sign-in providers and their client secrets, email templates, SMTP;
- the API keys — this Supabase has its own, and connected websites get them by themselves;
- roles you created yourself in Cloud: create them here first, or the copy stops at the first
  object one of them owns.

## BillionMail

Email marketing — newsletters, campaigns, contact lists — with **its own mail server**,
webmail and dashboard.

- **One mail server per server:** BillionMail is refused while **SixPanel Email** is
  installed (and SixPanel Email is refused while BillionMail holds the mail ports), while
  anything else answers on a mail port (25, 465, 587, 993, 995), or when a BillionMail
  already runs here. It needs a server with 2 GB of memory and 4 GB free on the disk.
- **The mail host name** you give (like `mail.example.com`) needs an **A record to this
  server that is DNS-only**, because mail does not pass through Cloudflare's proxy, and an
  **MX** record for your domain. Create the A record yourself: the panel writes no DNS-only
  record but SixPanel Email's own mail host. The DNS page expects the MX, and nothing in the
  panel proxies a name an MX of its zone delivers to. Ask your server provider to set the
  **reverse DNS (PTR)** of the server's address to the same name, or other mail servers
  treat your campaigns as spam.
- **Ports open to the internet:** 25, 465, 587, 993 and 995. If your provider has a cloud
  firewall, open them there too. Plain IMAP (143) and POP3 (110) are not published — mail
  clients use the TLS ports.
- **Its dashboard** is at `https://<your domain>/<a secret path>` — the whole address is
  under **Show** on the App tab, with the user (`billion`) and its password. Webmail is at
  `https://<your domain>/roundcube/`.
- **What differs from installing it by hand:** BillionMail manages its own containers
  through a Docker connection SixPanel gives it that reaches **its own containers only**
  (restart, logs, commands inside them) — never the server. Its feature for sending from
  several IP addresses needs the whole server and does not work here. Its built-in
  fail2ban is off (behind the web server it would see one visitor and ban everyone), and
  its fixed network range is not used (it belongs to Cloudflare).
- **Its environment is plain words:** BillionMail reads its `.env` itself, so a value is
  letters, digits and `. _ / : @ % + , ~ ^ -` only — no spaces, quotes or `=`.

---

## Tia

Tia can read Docker's state, your Docker apps, their services, logs, environment and
compose file — every secret as `****` — and start, stop, restart or update an app.
Creating a Docker app, changing its environment or compose file, and a clean-up wait for
your approval. She never reads a secret's value: to set one, she asks you to type it into
a field she cannot see.

## When something goes wrong

- **"Docker is not installed on this server"** — install it on **Software →
  Docker** first.
- **"… are not in this server's package lists"** — on Ubuntu, add the universe part of the
  archive (`sudo add-apt-repository universe`), then press Install again.
- **A compose file is refused** — each line says what to change. **Check** on the Compose
  file tab tries again without starting anything.
- **The app did not start** — the App tab says which step stopped and why; the **Logs** tab
  shows what its containers printed.
- **The website answers "being updated"** — the app is stopped, or still starting.
- **It slows down or restarts under load** — the App tab shows its memory against its
  ceiling; raise the ceiling on **Settings**.

## The software card on this page

A Docker app's page shows the **Docker** card; on **Software**, Docker says which Docker apps and which MongoDB or SQL Server databases use it. It is the same card as on **Software**: its state, its version, a waiting update and, for the owner, Restart (asked first, and refused while another job runs). **On Software** opens its card there, with who else uses it. See [Software, search and the system monitor](https://www.allsweb.com/sixpanel/docs/software-search-monitor).
