Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 68 additions & 37 deletions README.HOSTINGER-VPS-NO-DOMAIN.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,20 +180,26 @@ jobs:

cd "$APP_DIR"

if [ -d .git ]; then
git fetch --all --prune
git checkout main
git reset --hard ${{ github.sha }}
fi

printf '%s' "${{ secrets.SERVER_ENV_PRODUCTION }}" > server/.env.production

cat > .env.stack <<EOF
REGISTRY=docker.io
IMAGE_NAMESPACE=${{ steps.vars.outputs.namespace }}
IMAGE_TAG=${{ github.sha }}
POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
EOF
export REGISTRY=docker.io
export IMAGE_NAMESPACE=${{ steps.vars.outputs.namespace }}
export IMAGE_TAG=${{ github.sha }}
export POSTGRES_PASSWORD=${{ secrets.POSTGRES_PASSWORD }}
export ACCESS_TOKEN_SECRET=${{ secrets.ACCESS_TOKEN_SECRET }}
export JWT_ACCESS_SECRET=${{ secrets.ACCESS_TOKEN_SECRET }}

echo "${{ secrets.DOCKERHUB_TOKEN }}" | docker login docker.io -u "${{ secrets.DOCKERHUB_USERNAME }}" --password-stdin

docker compose --env-file .env.stack -f docker-compose.prod.yaml pull
docker compose --env-file .env.stack -f docker-compose.prod.yaml up -d
docker compose --env-file .env.stack -f docker-compose.prod.yaml run --rm ph-server sh -lc "pnpm prisma migrate deploy --schema=prisma/schema"
docker compose -f docker-compose.prod.yaml pull
docker compose -f docker-compose.prod.yaml up -d
docker compose -f docker-compose.prod.yaml run --rm ph-server sh -lc "pnpm prisma migrate deploy --schema=prisma/schema"
docker image prune -f
```

Expand Down Expand Up @@ -237,6 +243,9 @@ services:
image: ${REGISTRY}/${IMAGE_NAMESPACE}/ph-healthcare-client:${IMAGE_TAG}
container_name: ph-client
restart: unless-stopped
environment:
JWT_ACCESS_SECRET: ${JWT_ACCESS_SECRET}
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET}
depends_on:
- ph-server
networks:
Expand Down Expand Up @@ -319,11 +328,14 @@ FROM deps AS builder

COPY . .
RUN pnpm generate && pnpm build
RUN pnpm prune --prod

FROM base AS runner

ENV NODE_ENV=production

RUN npm install -g tsx@4.21.0

COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts
COPY --from=builder /app/node_modules ./node_modules
Expand All @@ -332,7 +344,7 @@ COPY --from=builder /app/prisma ./prisma

EXPOSE 5000

CMD ["pnpm", "start"]
CMD ["tsx", "dist/src/server.js"]
```

### 2.5 client/Dockerfile.prod
Expand All @@ -353,24 +365,19 @@ COPY . .
ARG NEXT_PUBLIC_API_BASE_URL
ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}
RUN pnpm exec next build
RUN pnpm prune --prod

FROM node:22-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

RUN corepack enable && corepack prepare pnpm@10.20.0 --activate

COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.ts ./next.config.ts

EXPOSE 3000

CMD ["pnpm", "exec", "next", "start", "-H", "0.0.0.0", "-p", "3000"]
CMD ["node", "server.js"]
```

### 2.6 server/prisma.config.ts
Expand Down Expand Up @@ -489,6 +496,11 @@ Set-Secret -Name "VPS_HOST" -Value $inputSecrets["VPS_HOST"]
Set-Secret -Name "VPS_USER" -Value $inputSecrets["VPS_USER"]
Set-Secret -Name "VPS_APP_DIR" -Value $inputSecrets["VPS_APP_DIR"]
Set-Secret -Name "CLIENT_PUBLIC_API_BASE_URL" -Value $inputSecrets["CLIENT_PUBLIC_API_BASE_URL"]
if ([string]::IsNullOrWhiteSpace($inputSecrets["ACCESS_TOKEN_SECRET"])) {
Set-Secret -Name "ACCESS_TOKEN_SECRET" -Value $serverEnv["ACCESS_TOKEN_SECRET"]
} else {
Set-Secret -Name "ACCESS_TOKEN_SECRET" -Value $inputSecrets["ACCESS_TOKEN_SECRET"]
}
Set-Secret -Name "POSTGRES_PASSWORD" -Value $postgresPassword
Set-Secret -Name "SERVER_ENV_PRODUCTION" -Value $serverEnvText

Expand All @@ -508,8 +520,9 @@ DOCKERHUB_USERNAME=your_dockerhub_username
DOCKERHUB_TOKEN=your_new_dockerhub_rw_token
VPS_HOST=your_vps_public_ip
VPS_USER=root
VPS_APP_DIR=/opt/apps/ph-healthcare
VPS_APP_DIR=/opt/apps/docker-nginx-deployment
CLIENT_PUBLIC_API_BASE_URL=http://your_vps_public_ip/api/v1
ACCESS_TOKEN_SECRET=your_access_token_secret
```

---
Expand All @@ -528,6 +541,7 @@ Set these in GitHub repository settings -> Secrets and variables -> Actions:
- VPS_APP_DIR
- CLIENT_PUBLIC_API_BASE_URL
- POSTGRES_PASSWORD
- ACCESS_TOKEN_SECRET
- SERVER_ENV_PRODUCTION

### 3.2 server/.env.production (local file, then uploaded into GitHub secret)
Expand Down Expand Up @@ -566,10 +580,10 @@ SUPER_ADMIN_PASSWORD=...

### 3.3 Runtime env files on VPS

These are generated by workflow automatically:
These are written/exported by workflow automatically:

- server/.env.production written from SERVER_ENV_PRODUCTION secret.
- .env.stack written by workflow with REGISTRY, IMAGE_NAMESPACE, IMAGE_TAG, POSTGRES_PASSWORD.
- Shell exports for deploy: REGISTRY, IMAGE_NAMESPACE, IMAGE_TAG, POSTGRES_PASSWORD, ACCESS_TOKEN_SECRET, JWT_ACCESS_SECRET.

---

Expand Down Expand Up @@ -669,13 +683,30 @@ Success condition:

---

## 8) Verify Deployment on VPS
## 8) Manual VPS Shell Prep (No .env.stack)

When running docker compose manually on VPS, export the compose variables once per shell session:

```bash
export REGISTRY=docker.io
export IMAGE_NAMESPACE=YOUR_DOCKERHUB_USERNAME
export IMAGE_TAG=YOUR_DEPLOYED_IMAGE_TAG
export POSTGRES_PASSWORD=YOUR_DB_PASSWORD
export ACCESS_TOKEN_SECRET=YOUR_ACCESS_SECRET
export JWT_ACCESS_SECRET=$ACCESS_TOKEN_SECRET
```

Then run compose commands normally.

---

## 9) Verify Deployment on VPS

```bash
cd /opt/apps/ph-healthcare
docker compose --env-file .env.stack -f docker-compose.prod.yaml ps
docker compose --env-file .env.stack -f docker-compose.prod.yaml logs --tail=200 ph-server
docker compose --env-file .env.stack -f docker-compose.prod.yaml logs --tail=200 ph-client
docker compose -f docker-compose.prod.yaml ps
docker compose -f docker-compose.prod.yaml logs --tail=200 ph-server
docker compose -f docker-compose.prod.yaml logs --tail=200 ph-client
curl -I http://YOUR_VPS_IP
```

Expand All @@ -685,28 +716,28 @@ http://YOUR_VPS_IP

---

## 9) Day-2 Operations
## 10) Day-2 Operations

Check status:

```bash
cd /opt/apps/ph-healthcare
docker compose --env-file .env.stack -f docker-compose.prod.yaml ps
docker compose -f docker-compose.prod.yaml ps
docker ps
```

Restart services:

```bash
docker compose --env-file .env.stack -f docker-compose.prod.yaml restart ph-server
docker compose --env-file .env.stack -f docker-compose.prod.yaml restart ph-client
docker compose --env-file .env.stack -f docker-compose.prod.yaml restart
docker compose -f docker-compose.prod.yaml restart ph-server
docker compose -f docker-compose.prod.yaml restart ph-client
docker compose -f docker-compose.prod.yaml restart
```

Run migration manually:

```bash
docker compose --env-file .env.stack -f docker-compose.prod.yaml run --rm ph-server sh -lc "pnpm prisma migrate deploy --schema=prisma/schema"
docker compose -f docker-compose.prod.yaml run --rm ph-server sh -lc "pnpm prisma migrate deploy --schema=prisma/schema"
```

Nginx checks:
Expand All @@ -719,16 +750,16 @@ systemctl status nginx --no-pager

---

## 10) Troubleshooting
## 11) Troubleshooting

### 10.1 Docker Hub push fails with 401 insufficient scopes
### 11.1 Docker Hub push fails with 401 insufficient scopes

Fix:
1. Create new Docker Hub token with Read/Write.
2. Update DOCKERHUB_TOKEN in GitHub secrets.
3. Re-run failed job.

### 10.2 Prisma schema not found in deploy job
### 11.2 Prisma schema not found in deploy job

Expected fixed configuration in this repo:
- workflow migrate command uses --schema=prisma/schema
Expand All @@ -739,10 +770,10 @@ Debug command:

```bash
cd /opt/apps/ph-healthcare
docker compose --env-file .env.stack -f docker-compose.prod.yaml run --rm ph-server sh -lc "ls -la prisma && ls -la prisma/schema"
docker compose -f docker-compose.prod.yaml run --rm ph-server sh -lc "ls -la prisma && ls -la prisma/schema"
```

### 10.3 UI not opening
### 11.3 UI not opening

Check Nginx + upstream:

Expand All @@ -756,7 +787,7 @@ ufw status

---

## 11) Security Notes
## 12) Security Notes

- Do not commit ci/github-secrets.input.env with real values.
- Rotate any token immediately if exposed.
Expand Down
11 changes: 3 additions & 8 deletions client/Dockerfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,16 @@ COPY . .
ARG NEXT_PUBLIC_API_BASE_URL
ENV NEXT_PUBLIC_API_BASE_URL=${NEXT_PUBLIC_API_BASE_URL}
RUN pnpm exec next build
RUN pnpm prune --prod

FROM node:22-alpine AS runner

WORKDIR /app
ENV NODE_ENV=production

RUN corepack enable && corepack prepare pnpm@10.20.0 --activate

COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
COPY --from=builder /app/next.config.ts ./next.config.ts

EXPOSE 3000

CMD ["pnpm", "exec", "next", "start", "-H", "0.0.0.0", "-p", "3000"]
CMD ["node", "server.js"]
2 changes: 1 addition & 1 deletion client/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
};

export default nextConfig;
6 changes: 4 additions & 2 deletions server/Dockerfile.prod
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ FROM deps AS builder

COPY . .
RUN pnpm generate && pnpm build
RUN pnpm prune --prod

FROM base AS runner

ENV NODE_ENV=production

RUN npm install -g tsx@4.21.0

COPY --from=builder /app/package.json /app/pnpm-lock.yaml ./
COPY --from=builder /app/prisma.config.ts ./prisma.config.ts
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/src ./src
COPY --from=builder /app/prisma ./prisma

EXPOSE 5000

CMD ["pnpm", "start"]
CMD ["tsx", "dist/src/server.js"]