fix(deploy): switch from nixpacks to Dockerfile + change branch to main
Some checks failed
Build & Deploy to EasyPanel / build-and-deploy (push) Has been cancelled
Lint & Test / build-check (push) Has been cancelled

Nixpacks auto-detect could not find a 'start' script in package.json
and bailed out. Astro builds to static files in dist/ — there is no
Node server to start. Switching to a Dockerfile + nginx fixes the
'No start command could be found' error from EasyPanel.

The workflow also pointed at the source-code branch, but the panel's
git source ref for the dealplustech-astro service is 'main', so the
trigger was firing for the wrong ref. Both workflows now run on push
to main.

- Dockerfile: multi-stage node:20-alpine build + nginx:1.27-alpine serve
- nginx.conf: gzip, security headers, 1-year cache for hashed assets,
  try_files fallback for UTF-8 slugs (Astro file-based routing)
- .dockerignore: keep build context small (skip CI, docs, .gitea, IDE)
- build-and-deploy.yml + lint.yml: branch source-code -> main
- docs/ci-setup.md: corrected project + service names (customerwebsite
  / dealplustech-astro), documented the Dockerfile rationale, added a
  note for the 'Failed to sync changes' server-side error
This commit is contained in:
hermes
2026-06-09 10:28:46 +07:00
parent 3efaf4d661
commit d73e48351f
6 changed files with 165 additions and 18 deletions

34
.dockerignore Normal file
View File

@@ -0,0 +1,34 @@
# Build artifacts
node_modules/
dist/
.astro/
# CI / Gitea
.gitea/
.github/
# Local dev / IDE
.DS_Store
.vscode/
.idea/
*.log
*.tmp
.env
.env.*
!.env.example
# Git
.git/
.gitignore
# Docker (don't include Docker files in the build context of themselves)
Dockerfile
.dockerignore
docker-compose*.yml
# Docs
docs/
# Misc
coverage/
*.tsbuildinfo

View File

@@ -3,7 +3,7 @@ name: Build & Deploy to EasyPanel
on: on:
push: push:
branches: branches:
- source-code - main
workflow_dispatch: workflow_dispatch:
jobs: jobs:

View File

@@ -3,10 +3,10 @@ name: Lint & Test
on: on:
push: push:
branches: branches:
- source-code - main
pull_request: pull_request:
branches: branches:
- source-code - main
jobs: jobs:
build-check: build-check:

29
Dockerfile Normal file
View File

@@ -0,0 +1,29 @@
# =====================================================================
# Stage 1: Build the Astro static site
# =====================================================================
FROM node:20-alpine AS builder
WORKDIR /app
# Install deps with cache layer
COPY package*.json ./
RUN npm ci --no-audit --no-fund
# Build
COPY . .
RUN npm run build
# =====================================================================
# Stage 2: Serve with nginx
# =====================================================================
FROM nginx:1.27-alpine
# Astro outputs to ./dist by default
COPY --from=builder /app/dist /usr/share/nginx/html
# nginx config: SPA-friendly, gzip, cache headers for static assets
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -1,6 +1,6 @@
# CI/CD Setup — EasyPanel Deploy # CI/CD Setup — EasyPanel Deploy
Push to `source-code` triggers `build-and-deploy.yml`: Push to `main` triggers `build-and-deploy.yml`:
1. Builds the Astro static site into `dist/` 1. Builds the Astro static site into `dist/`
2. Uploads `dist/` as a 7-day artifact 2. Uploads `dist/` as a 7-day artifact
3. Calls EasyPanel tRPC endpoint to trigger a redeploy 3. Calls EasyPanel tRPC endpoint to trigger a redeploy
@@ -9,11 +9,14 @@ Push to `source-code` triggers `build-and-deploy.yml`:
Go to **Settings → Actions → Secrets** and add three secrets: Go to **Settings → Actions → Secrets** and add three secrets:
| Name | Example | Where to get it | | Name | Value (for this site) | Where to get it |
|---|---|---| |---|---|---|
| `EASYPANEL_TOKEN` | `cmq61ao6h000207qn6mmp2i7u` | EasyPanel → profile/settings → API tokens | | `EASYPANEL_TOKEN` | `cmq61xwrv000407qn9e2hhfuw` | EasyPanel → profile/settings → API tokens |
| `EASYPANEL_PROJECT_NAME` | `dealplustech-astroreal` | EasyPanel → project name in the dashboard | | `EASYPANEL_PROJECT_NAME` | `customerwebsite` | EasyPanel → project name in the dashboard |
| `EASYPANEL_SERVICE_NAME` | `web` (or whatever you named the app service) | EasyPanel → service name inside the project | | `EASYPANEL_SERVICE_NAME` | `dealplustech-astro` | EasyPanel → service name inside the project |
> ⚠️ Project name ≠ repo name. The site lives in the `customerwebsite`
> project on the panel; the repo is `dealplustech-astroreal`.
If any of these are empty, the workflow logs a warning and skips the deploy If any of these are empty, the workflow logs a warning and skips the deploy
trigger. The build still runs and the artifact is still uploaded. trigger. The build still runs and the artifact is still uploaded.
@@ -22,20 +25,36 @@ trigger. The build still runs and the artifact is still uploaded.
The service on the panel side must be: The service on the panel side must be:
- **Type: `app`** (Dockerfile-based). The trigger calls - **Type: `app`**
`services.app.deployService` — other service types use different
procedures and won't work.
- **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`) - **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`)
on branch `source-code`. on branch **`main`** (not `source-code`).
- **Build command:** `npm run build` - **Build type: `dockerfile`**, **file: `Dockerfile`** at the repo root.
- **Output dir:** `dist` > The workflow used to trigger nixpacks builds, but nixpacks expects a
> `start` script in `package.json` and an Astro static site doesn't
> have one. Switching to a Dockerfile + nginx fixes that.
- **Port: `80`**
If you need a different service type later, swap the endpoint in If you need a different service type later, swap the endpoint in
`.gitea/workflows/build-and-deploy.yml` to the matching procedure: `.gitea/workflows/build-and-deploy.yml` to the matching procedure:
- `services.app.deployService` (Dockerfile) - `services.app.deployService` (Dockerfile / app)
- `services.box.rebuildDockerImage` (low-level) - `services.box.rebuildDockerImage` (low-level)
- `services.compose.deployService` (docker-compose) - `services.compose.deployService` (docker-compose)
## Why Dockerfile and not nixpacks
Astro builds to static files in `dist/` — there is no Node server to run.
Nixpacks tries to find a `start` command and fails. The Dockerfile in
this repo:
1. **Stage 1** (`node:20-alpine`): runs `npm ci` then `npm run build` to
produce `dist/`.
2. **Stage 2** (`nginx:1.27-alpine`): copies `dist/` to nginx's web root
and serves it.
`nginx.conf` ships gzip, security headers, 1-year cache for hashed
assets, and a `try_files` fallback for client-side routes (Astro's
file-based routing produces UTF-8 slugs).
## Verifying the trigger payload ## Verifying the trigger payload
If the deploy runs but the panel rejects it, the response in the workflow If the deploy runs but the panel rejects it, the response in the workflow
@@ -47,10 +66,20 @@ To test the payload shape from your local machine:
```bash ```bash
curl -sS -X POST \ curl -sS -X POST \
"https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \ "https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \
-H "Authorization: Bearer *** \ -H "Authorization: Bearer *** -H "Content-Type: application/json" \
-H "Content-Type: application/json" \ -d '{"json":{"projectName":"customerwebsite","serviceName":"dealplustech-astro"}}'
-d '{"json":{"projectName":"YOUR_PROJECT","serviceName":"YOUR_SERVICE"}}'
``` ```
A 2xx response = the panel accepted the trigger. The service will start A 2xx response = the panel accepted the trigger. The service will start
rebuilding/redploying in the EasyPanel dashboard. rebuilding/redploying in the EasyPanel dashboard.
## Common failure: "Failed to sync changes" (HTTP 500)
This is a **server-side** error, not a payload problem. It usually means:
- The EasyPanel service is currently in a state that can't be redeployed
(e.g. the previous container is stuck, or a build is in progress).
- The Docker daemon on the panel host is busy.
Try again in a few seconds. If it persists, open the EasyPanel dashboard
and check the service's container state.

55
nginx.conf Normal file
View File

@@ -0,0 +1,55 @@
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
root /usr/share/nginx/html;
index index.html;
# gzip
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types
text/plain
text/css
text/javascript
text/xml
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml
font/ttf
font/otf;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Cache static assets aggressively (Astro hashes filenames)
location ~* \.(?:js|css|woff2?|ttf|otf|eot|svg|jpg|jpeg|png|webp|avif|ico)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}
# Don't cache HTML
location ~* \.html$ {
expires -1;
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# Thai URL slugs (Astro file-based routing produces paths with UTF-8 chars)
location / {
try_files $uri $uri/ $uri.html /index.html;
}
# healthcheck
location /healthz {
access_log off;
return 200 "ok\n";
add_header Content-Type text/plain;
}
}