fix(deploy): switch from nixpacks to Dockerfile + change branch to main
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:
34
.dockerignore
Normal file
34
.dockerignore
Normal 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
|
||||
@@ -3,7 +3,7 @@ name: Build & Deploy to EasyPanel
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- source-code
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
|
||||
@@ -3,10 +3,10 @@ name: Lint & Test
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- source-code
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- source-code
|
||||
- main
|
||||
|
||||
jobs:
|
||||
build-check:
|
||||
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal 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;"]
|
||||
@@ -1,6 +1,6 @@
|
||||
# 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/`
|
||||
2. Uploads `dist/` as a 7-day artifact
|
||||
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:
|
||||
|
||||
| Name | Example | Where to get it |
|
||||
| Name | Value (for this site) | Where to get it |
|
||||
|---|---|---|
|
||||
| `EASYPANEL_TOKEN` | `cmq61ao6h000207qn6mmp2i7u` | EasyPanel → profile/settings → API tokens |
|
||||
| `EASYPANEL_PROJECT_NAME` | `dealplustech-astroreal` | EasyPanel → project name in the dashboard |
|
||||
| `EASYPANEL_SERVICE_NAME` | `web` (or whatever you named the app service) | EasyPanel → service name inside the project |
|
||||
| `EASYPANEL_TOKEN` | `cmq61xwrv000407qn9e2hhfuw` | EasyPanel → profile/settings → API tokens |
|
||||
| `EASYPANEL_PROJECT_NAME` | `customerwebsite` | EasyPanel → project name in the dashboard |
|
||||
| `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
|
||||
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:
|
||||
|
||||
- **Type: `app`** (Dockerfile-based). The trigger calls
|
||||
`services.app.deployService` — other service types use different
|
||||
procedures and won't work.
|
||||
- **Type: `app`**
|
||||
- **Source: Git**, pointing at this repo (`kunthawat/dealplustech-astroreal`)
|
||||
on branch `source-code`.
|
||||
- **Build command:** `npm run build`
|
||||
- **Output dir:** `dist`
|
||||
on branch **`main`** (not `source-code`).
|
||||
- **Build type: `dockerfile`**, **file: `Dockerfile`** at the repo root.
|
||||
> 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
|
||||
`.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.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
|
||||
|
||||
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
|
||||
curl -sS -X POST \
|
||||
"https://panelwebsite.moreminimore.com/api/trpc/services.app.deployService" \
|
||||
-H "Authorization: Bearer *** \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"json":{"projectName":"YOUR_PROJECT","serviceName":"YOUR_SERVICE"}}'
|
||||
-H "Authorization: Bearer *** -H "Content-Type: application/json" \
|
||||
-d '{"json":{"projectName":"customerwebsite","serviceName":"dealplustech-astro"}}'
|
||||
```
|
||||
|
||||
A 2xx response = the panel accepted the trigger. The service will start
|
||||
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
55
nginx.conf
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user