Add Docker image, example compose file and Gitea build workflow
- Dockerfile: node:24-alpine, production deps only, runs as the node user, healthcheck on /healthz. No volume, because the server keeps no state. - docker-compose.yml: example for the VPS. It publishes no ports, since Docker's port publishing bypasses ufw and would skip NPM's IP access list. It joins NPM's network instead. - .gitea/workflows/docker.yml: on v*-prod* tags, builds on the self-hosted runner and pushes v0idf1sh/sdp-dashboard:<tag> and :latest using the DHUB_UNAME and DHUB_PASS secrets. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b6ef6a195c
commit
c2cf725819
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
.git
|
||||||
|
.gitea
|
||||||
|
.vscode
|
||||||
|
node_modules
|
||||||
|
npm-debug.log*
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
docs
|
||||||
|
images
|
||||||
|
*.md
|
||||||
|
Dockerfile
|
||||||
|
docker-compose*.yml
|
||||||
43
.gitea/workflows/docker.yml
Normal file
43
.gitea/workflows/docker.yml
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
# Builds the image and pushes it to Docker Hub when a production tag (e.g. v1.1.0-prod1) is pushed.
|
||||||
|
# Restarting the container on the VPS is done by hand for now.
|
||||||
|
name: Build and push image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- 'v*-prod*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: v0idf1sh/sdp-dashboard
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Check out
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Log in to Docker Hub
|
||||||
|
env:
|
||||||
|
DHUB_UNAME: ${{ secrets.DHUB_UNAME }}
|
||||||
|
DHUB_PASS: ${{ secrets.DHUB_PASS }}
|
||||||
|
run: echo "$DHUB_PASS" | docker login --username "$DHUB_UNAME" --password-stdin
|
||||||
|
|
||||||
|
- name: Build
|
||||||
|
run: |
|
||||||
|
docker build \
|
||||||
|
--pull \
|
||||||
|
--label "org.opencontainers.image.version=${{ gitea.ref_name }}" \
|
||||||
|
--label "org.opencontainers.image.revision=${{ gitea.sha }}" \
|
||||||
|
-t "$IMAGE:${{ gitea.ref_name }}" \
|
||||||
|
-t "$IMAGE:latest" \
|
||||||
|
.
|
||||||
|
|
||||||
|
- name: Push
|
||||||
|
run: |
|
||||||
|
docker push "$IMAGE:${{ gitea.ref_name }}"
|
||||||
|
docker push "$IMAGE:latest"
|
||||||
|
|
||||||
|
- name: Log out
|
||||||
|
if: always()
|
||||||
|
run: docker logout
|
||||||
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
FROM node:24-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# install dependencies first so code-only changes reuse this layer
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci --omit=dev && npm cache clean --force
|
||||||
|
|
||||||
|
COPY src ./src
|
||||||
|
|
||||||
|
# no volume: sessions live in each user's browser, so the container keeps no state
|
||||||
|
USER node
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
|
||||||
|
CMD wget -qO- http://127.0.0.1:3000/healthz || exit 1
|
||||||
|
|
||||||
|
CMD ["node", "src/server.js"]
|
||||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Example compose file for the VPS. Copy src/env.example to .env next to this file and fill it in.
|
||||||
|
# Nginx Proxy Manager forwards the subdomain to sdp-dashboard:3000 and enforces the IP allow list.
|
||||||
|
|
||||||
|
services:
|
||||||
|
sdp-dashboard:
|
||||||
|
image: v0idf1sh/sdp-dashboard:latest
|
||||||
|
container_name: sdp-dashboard
|
||||||
|
restart: unless-stopped
|
||||||
|
env_file: .env
|
||||||
|
environment:
|
||||||
|
# must match the redirect URI registered in the Zoho API console, minus /auth/callback
|
||||||
|
PUBLIC_URL: https://dashboard.example.com
|
||||||
|
# No published ports on purpose. Docker's port publishing bypasses ufw, so "3000:3000" would
|
||||||
|
# let anyone reach the app directly and skip NPM's access list. NPM reaches it over the
|
||||||
|
# shared network instead, using the container name as the forward hostname.
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
|
networks:
|
||||||
|
- proxy
|
||||||
|
|
||||||
|
networks:
|
||||||
|
proxy:
|
||||||
|
# the network Nginx Proxy Manager's container is on; check with `docker network ls`
|
||||||
|
external: true
|
||||||
|
name: ${NPM_NETWORK:-npm_default}
|
||||||
Loading…
Reference in New Issue
Block a user