Portainer: deploy from the Git repository (web editor cannot build)

Verified on Portainer 2.45: an inline/web-editor stack with `build: .`
fails with "compose build operation failed: failed to solve: failed to read
dockerfile: open …/Dockerfile: no such file or directory".

- README + docs/QUICKSTART-PORTAINER: Route 1 = Repository deploy (Portainer
  clones the repo, so build: . resolves), staged so NocoDB comes up first and
  the base id/token are added afterwards; Route 2 = web editor with a
  hand-built image and the build: line removed
- docker-compose.allinone.yml header: same correction
- validated with: docker compose -f docker-compose.allinone.yml config
This commit is contained in:
2026-10-06 16:41:03 +08:00
parent 5d9db48a26
commit 64165ce5aa
3 changed files with 133 additions and 89 deletions
+46 -12
View File
@@ -167,30 +167,64 @@ Nothing in Telegram? See [Troubleshooting](#troubleshooting).
## Deploy on Portainer (GUI)
Portainer runs the exact same file — it is a normal compose stack. Everything happens in the browser.
Portainer runs the same file — it is a normal compose stack. **One catch first:** the monitor's image is built from this repository (`build: .`), and Portainer's *Web editor* / *Upload* options have no build context, so they fail with:
1. **Stacks → Add stack**.
```
compose build operation failed: failed to read dockerfile:
open /volume1/@docker/tmp/…/Dockerfile: no such file or directory
```
So pick one of these two routes — the first needs no shell at all.
### Route 1 — deploy from the Git repository (recommended)
Portainer clones the repo itself, so `build: .` resolves normally.
1. **Stacks → Add stack → Repository**.
2. **Name**: `carousell-monitor`.
3. **Build method**: *Web editor* (or upload `docker-compose.allinone.yml` from this repo).
4. Paste the whole content of [`docker-compose.allinone.yml`](docker-compose.allinone.yml).
5. **Environment variables** — fill in the ones the file references (Portainer lists them for you):
3. **Repository URL**: `https://github.com/hoelee/carousell-monitor` · **Reference**: `refs/heads/main` · **Compose path**: `docker-compose.allinone.yml`.
4. **Environment variables** — add these (Portainer lists the ones the file references):
| Variable | Value |
|---|---|
| `NC_AUTH_JWT_SECRET` | output of `openssl rand -hex 32` |
| `NOCODB_TOKEN` | the `nc_pat_…` token |
| `NOCODB_BASE_ID` | the id copied from the base URL |
| `TELEGRAM_BOT_TOKEN` | from @BotFather |
| `TELEGRAM_CHAT_ID` | from @userinfobot |
| `NOCODB_PORT` | optional, default `8080` |
> ⚠️ Portainer **masks secret-looking values you type into this panel** and saves the mask (`***`) with the stack. The credentials then silently stop working on the next stack update. If you plan to edit this stack in Portainer again, put the real values directly in the YAML in the web editor instead of in the env panel.
6. **Deploy the stack.** For a first install you want NocoDB first: after the deploy finishes, open `http://<your-host>:8080`, create the account and the base ([Step 3](#step-3--start-nocodb-and-create-the-base)), then paste the base id + token into the same field(s) and press **Update the stack**.
7. Verify in Portainer: the container list shows `carousell-nocodb` and `carousell-monitor` both **running/healthy**, and the monitor's logs show the `ready:` line.
Leave `NOCODB_TOKEN` and `NOCODB_BASE_ID` **empty for now** — you cannot have them until NocoDB is running.
5. **Deploy the stack.** `carousell-nocodb` comes up healthy; `carousell-monitor` will restart in a short loop logging `NOCODB_TOKEN not set`. That is expected — stop that container for now if the noise bothers you.
6. Open `http://<your-host>:8080` and follow [Step 3](#step-3--start-nocodb-and-create-the-base): create the account, create a base, copy the base id, create an API token.
7. Back in Portainer: **Stacks → carousell-monitor → Editor** (or *Environment variables*) and fill in `NOCODB_BASE_ID` + `NOCODB_TOKEN`, then **Update the stack**. The monitor starts its real loop.
Managing it afterwards is the same screen: **Stacks → carousell-monitor → Update the stack** (edit YAML/env), **containers → logs/restart**, **volumes** for backups.
> ⚠️ Portainer **masks secret-looking values that you type into its environment panel** and stores the mask (`***`) with the stack, so a value can silently stop working after a later stack update. If you hit that, put the real values directly in the YAML in the **Editor** tab instead of the panel.
8. Verify: **Containers** shows `carousell-nocodb` and `carousell-monitor` healthy, and the monitor's log shows the `ready: …` line.
Prefer the repository build? *Add stack → Repository* with `https://github.com/hoelee/carousell-monitor` and compose path `docker-compose.allinone.yml`. Note that Portainer's repository stacks do a full `git clone` on every deploy, so the web-editor or upload route is faster for a single file.
### Route 2 — Web editor, with the image built by hand first
Use this if you cannot give Portainer access to your Git host, or you want the stack file to be self-contained.
```bash
# on the Docker host (or anywhere that can reach its daemon)
git clone https://github.com/hoelee/carousell-monitor.git
cd carousell-monitor
docker build -t carousell-monitor:latest .
```
Then **Stacks → Add stack → Web editor**, paste [`docker-compose.allinone.yml`](docker-compose.allinone.yml) and **delete the `build: .` line** from the monitor service — Portainer's web-editor stack directory contains no Dockerfile, so a `build:` entry can only fail there. Fill in the same variables as above, deploy NocoDB first, then add the base id and token and **Update the stack**.
Afterwards, updating the code means: `git pull && docker build -t carousell-monitor:latest .` on the host, then **Update the stack** in Portainer.
### Managing it afterwards
| Task | Where |
|---|---|
| Change a search / filters | NocoDB UI — nothing to redeploy |
| Change credentials or tuning | **Stacks → carousell-monitor → Editor** (or *Environment variables*) → **Update the stack** |
| See logs | **Containers → carousell-monitor → Logs** |
| Restart | **Containers → carousell-monitor → Restart** (state lives in NocoDB — nothing is lost) |
| Back up | **Volumes → nocodb-data** — that volume *is* your data |
| Upgrade NocoDB | change the image tag in the Editor → **Update the stack** (back up the volume first) |
---
+10 -5
View File
@@ -18,11 +18,16 @@
#
# Portainer
# ---------
# Stacks -> Add stack -> Web editor -> paste this whole file -> fill in the
# environment variables -> Deploy the stack.
# (Recommended: replace the ${...} placeholders with real values in the
# editor. Portainer masks secret-looking values that you type into its
# "Environment variables" panel, which can break a later stack update.)
# Deploy it from the Git repository:
# Stacks -> Add stack -> Repository -> URL <this repo> ->
# Compose path: docker-compose.allinone.yml -> fill in the environment
# variables -> Deploy the stack.
# Portainer's "Web editor"/"Upload" options have no build context, so this
# file's `build: .` fails there (failed to read dockerfile: ... no such file).
# For those, build the image on the host first
# (git clone && docker build -t carousell-monitor:latest .)
# and delete the `build: .` line below.
# See docs/QUICKSTART-PORTAINER.md for the click-by-click version.
#
# Upgrading NocoDB: change the image tag below, then
# docker compose -f docker-compose.allinone.yml up -d
+77 -72
View File
@@ -1,52 +1,39 @@
# Quick start on Portainer (click-by-click)
For a fresh machine, no shell needed. Portainer runs the same compose file as the CLI flow — see the [README](../README.md) for that version.
For a fresh machine, no shell needed. Portainer runs the same compose file as the CLI flow — see the [README](../README.md#quick-start-cli) for that version.
**Before you start:** Portainer must already be up and connected to a Docker endpoint, and Portainer's own container needs access to the Docker socket (the standard install does). NocoDB will need one free host port — `8080` by default.
**Before you start:** Portainer must already be up and connected to a Docker endpoint, and its container needs access to the Docker socket (the standard install does). NocoDB needs one free host port — `8080` by default.
**One thing to know up front:** the monitor's image is built from this repository (`build: .`). Portainer's *Web editor* and *Upload* options have no build context, so they cannot build it:
```
compose build operation failed: failed to solve: failed to read dockerfile:
open /volume1/@docker/tmp/buildkit-mount…/Dockerfile: no such file or directory
```
That leaves two routes. **Route 1 needs no shell.**
---
## 1. Create the NocoDB container first
Because you cannot paste the base id and token until NocoDB exists, do this in two deploys.
## Route 1 — deploy from the Git repository (recommended)
1. **Stacks → Add stack**
2. **Name:** `carousell-monitor`
3. **Build method:** *Web editor*
4. Paste the **first service only** for now — the `nocodb:` block from [`docker-compose.allinone.yml`](../docker-compose.allinone.yml) plus the closing `volumes:` / `networks:` sections:
3. **Build method:** *Repository*
4. **Repository URL:** `https://github.com/hoelee/carousell-monitor`
**Reference:** `refs/heads/main`
**Compose path:** `docker-compose.allinone.yml`
5. **Environment variables** — add:
```yaml
services:
nocodb:
image: nocodb/nocodb:2026.09.0
container_name: carousell-nocodb
restart: unless-stopped
environment:
PORT: "8080"
NC_AUTH_JWT_SECRET: <paste output of: openssl rand -hex 32>
NC_DISABLE_TELE: "true"
NC_ALLOW_LOCAL_HOOKS: "false"
volumes:
- nocodb-data:/usr/app/data
ports:
- "8080:8080"
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8080/api/v1/health >/dev/null 2>&1 || exit 1"]
interval: 30s
timeout: 10s
retries: 5
start_period: 60s
networks:
- carousell
| Variable | Value |
|---|---|
| `NC_AUTH_JWT_SECRET` | output of `openssl rand -hex 32` |
| `TELEGRAM_BOT_TOKEN` | from @BotFather |
| `TELEGRAM_CHAT_ID` | from @userinfobot |
| `NOCODB_PORT` | optional, default `8080` |
volumes:
nocodb-data:
networks:
carousell:
```
5. **Deploy the stack.** Wait for `carousell-nocodb` to show as running/healthy.
Add `NOCODB_TOKEN` and `NOCODB_BASE_ID` too if you already have them (e.g. you are reusing an existing NocoDB). On a fresh install you cannot: they do not exist yet. Leave them empty.
6. **Deploy the stack.** `carousell-nocodb` comes up healthy. `carousell-monitor` will restart in a short loop logging `NOCODB_TOKEN not set` — expected, it needs the values from step 8. Stop that container for now if you prefer a clean list.
## 2. Create the base and collect the two values
@@ -55,52 +42,70 @@ Open `http://<your-host>:8080`:
1. Create the admin account.
2. Create a base (`+ New Base`) — call it `Carousell`.
3. Copy the **base id** from the URL (`…/nc/base/<BASE_ID>/…`).
4. Avatar (bottom-left) → **Account Settings → Tokens → Create token**, and copy the `nc_pat_…` value. It is shown once.
4. Avatar (bottom-left) → **Account Settings → Tokens → Create token**, copy the `nc_pat_…` value. It is shown once.
5. Do not create tables by hand — the monitor does that on start.
## 3. Add the monitor to the same stack
## 3. Hand the values to the monitor
1. Get the Telegram values first ([docs/TELEGRAM-SETUP.md](TELEGRAM-SETUP.md)) — bot token from @BotFather, chat id from @userinfobot.
2. Back in Portainer: **Stacks → carousell-monitor → Editor** tab.
3. Replace the whole file with the content of [`docker-compose.allinone.yml`](../docker-compose.allinone.yml), **with the real values written in directly** instead of `${...}` placeholders:
1. **Stacks → carousell-monitor → Editor** (or *Environment variables*).
2. Fill in `NOCODB_BASE_ID` and `NOCODB_TOKEN`, then **Update the stack**.
3. Portainer recreates the monitor. **Containers → carousell-monitor → Logs** should now end with one line:
```yaml
environment:
NOCODB_URL: http://nocodb:8080
NOCODB_TOKEN: nc_pat_your_token_here
NOCODB_BASE_ID: your_base_id_here
TELEGRAM_BOT_TOKEN: 8123456789:AAF...
TELEGRAM_CHAT_ID: 123456789
```
ready: listings=… settings=… ignored_sellers=… ignored_keywords=… seen=0
```
and the same for `NC_AUTH_JWT_SECRET` / `NC_DISABLE_TELE` in the `nocodb` service.
and the container status should be `healthy` within a couple of minutes.
> **Why inline values instead of Portainer's environment panel?** Portainer CE masks secret-looking values that are entered through the UI and stores the mask with the stack. On the next stack update the container receives `***` and silently stops authenticating. Editing the YAML keeps the real value on your Portainer host, which is where a stack file's secrets live anyway.
4. **Update the stack.** Portainer recreates the services that changed and adds `carousell-monitor`.
5. **Containers → carousell-monitor → Logs**: expect one line starting with `ready: listings=… settings=… seen=0`.
> ⚠️ **Portainer masks secret-looking values typed into its environment panel** and stores the mask (`***`) with the stack — on a later stack update the container receives `***` and authentication silently fails. If that happens, put the real values directly in the YAML in the **Editor** tab instead of the panel.
## 4. Start using it
In NocoDB, open the base — four tables are now present. Add your first watch to **`Settings`** (see [docs/NOCODB-SETUP.md](NOCODB-SETUP.md#3-the-tables-the-monitor-creates)) and give it a minute. Then untick `notified` on any `Listings` row to force a test alert.
In NocoDB, open the base — four tables now exist (`Listings`, `Settings`, `IgnoredSellers`, `IgnoredKeywords`). Add your first watch to **`Settings`** ([details](../docs/NOCODB-SETUP.md#3-the-tables-the-monitor-creates)), give it a minute, then untick `notified` on any `Listings` row to force a test alert into Telegram.
## 5. Everyday maintenance in Portainer
---
## Route 2 — Web editor, with the image built by hand first
Use this when Portainer cannot reach your Git host, or you want a self-contained stack file.
```bash
# on the Docker host (or anywhere with access to its daemon)
git clone https://github.com/hoelee/carousell-monitor.git
cd carousell-monitor
docker build -t carousell-monitor:latest .
```
Then:
1. **Stacks → Add stack → Web editor**, name it `carousell-monitor`.
2. Paste [`docker-compose.allinone.yml`](../docker-compose.allinone.yml) and **remove the `build: .` line** from the `carousell-monitor` service — a `build:` entry in a web-editor stack can only fail (no Dockerfile in Portainer's stack directory).
3. Fill in `NC_AUTH_JWT_SECRET`, `NOCODB_PORT`, and the Telegram values. Deploy, then follow §2 and §3 above to add the base id and token.
Updating the code later:
```bash
git pull && docker build -t carousell-monitor:latest .
```
then **Update the stack** in Portainer.
---
## Everyday maintenance in Portainer
| Task | Where |
|---|---|
| Change a search / filters | NocoDB UI — nothing to redeploy |
| Change credentials or tuning | **Stacks → carousell-monitor → Editor** → edit YAML → **Update the stack** |
| See why it is unhealthy | **Containers → carousell-monitor → Logs**, then the `Listings`/`Settings` tables for real progress |
| Restart | **Containers → carousell-monitor → Restart** (state is in NocoDB, nothing is lost) |
| Update this project | pull the new code on the host, then **Editor** → *Update the stack* with `Re-pull image`/rebuild enabled, or `docker compose up -d --build` on the CLI |
| Back up | **Volumes → nocodb-data** (all data) — or NocoDB's own base export |
| Change a search / filter | NocoDB UI — nothing to redeploy |
| Change credentials or tuning | **Stacks → carousell-monitor → Editor** → **Update the stack** |
| Deploy a code change | Route 1: **Update the stack** (Portainer re-clones). Route 2: `git pull && docker build …`, then **Update the stack** |
| See why it is unhealthy | **Containers → carousell-monitor → Logs**, then `Listings` / `Settings` in NocoDB for the real progress |
| Restart | **Containers → carousell-monitor → Restart** — state is in NocoDB, nothing is lost |
| Back up | **Volumes → nocodb-data** (all data), or NocoDB's own base export |
| Upgrade NocoDB | edit the image tag in the Editor → **Update the stack**; back the volume up first |
## 6. Optional: deploy from the repository instead
## Notes and limits
**Stacks → Add stack → Repository**, with:
| Field | Value |
|---|---|
| Repository URL | `https://github.com/hoelee/carousell-monitor` |
| Reference | `refs/heads/main` |
| Compose path | `docker-compose.allinone.yml` |
Set `NOCODB_BASE_ID` / `NOCODB_TOKEN` / `TELEGRAM_*` / `NC_AUTH_JWT_SECRET` in the environment panel for this one (they are not in git). Be aware of the masking caveat above, and that Portainer clones the repository on every deploy.
- A **Repository** stack re-clones the repo on every deploy, so the update is always a full fetch of `main`. That is fine here (the repo is small) and it means you get fixes by pressing one button.
- Portainer deploys standalone compose stacks for this — no Swarm involved.
- GitOps updates (auto-redeploy when the repo changes) work too: enable them in the stack settings after the first deploy.