Добавить статичный Docker-деплой
Build and deploy / deploy (push) Has been cancelled

This commit is contained in:
2026-05-17 22:35:08 +03:00
parent dcdc040918
commit fc603f9579
7 changed files with 219 additions and 1 deletions
+9
View File
@@ -0,0 +1,9 @@
.git
.gitea
.webarchive
deploy
web/.next
web/node_modules
web/out
README.md
*.log
+126
View File
@@ -0,0 +1,126 @@
name: Build and deploy
on:
push:
branches:
- master
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
env:
DOMAIN: ${{ vars.DOMAIN }}
IMAGE: ${{ vars.IMAGE }}
IMAGE_REPO: ${{ vars.IMAGE_REPO }}
PUSH_LATEST: ${{ vars.PUSH_LATEST }}
REGISTRY: ${{ vars.REGISTRY }}
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
STACK_NAME: ${{ vars.STACK_NAME }}
TRAEFIK_NETWORK: ${{ vars.TRAEFIK_NETWORK }}
TRAEFIK_ENTRYPOINT: ${{ vars.TRAEFIK_ENTRYPOINT }}
DEPLOY_HOST: ${{ vars.DEPLOY_HOST }}
DEPLOY_PORT: ${{ vars.DEPLOY_PORT }}
DEPLOY_USER: ${{ vars.DEPLOY_USER }}
DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Prepare image name
shell: bash
run: |
set -euo pipefail
short_sha="${GITHUB_SHA:-${GITEA_SHA:-manual}}"
short_sha="${short_sha:0:12}"
if [ -z "${IMAGE}" ]; then
: "${IMAGE_REPO:?Set IMAGE or IMAGE_REPO in Gitea variables}"
IMAGE="${IMAGE_REPO}:${short_sha}"
fi
if [ -z "${IMAGE_REPO}" ]; then
IMAGE_REPO="${IMAGE%:*}"
fi
{
echo "IMAGE=${IMAGE}"
echo "IMAGE_REPO=${IMAGE_REPO}"
echo "IMAGE_TAG=${short_sha}"
echo "STACK_NAME=${STACK_NAME:-progcode}"
echo "TRAEFIK_NETWORK=${TRAEFIK_NETWORK:-traefik-public}"
echo "TRAEFIK_ENTRYPOINT=${TRAEFIK_ENTRYPOINT:-websecure}"
echo "DEPLOY_PORT=${DEPLOY_PORT:-22}"
} >> "${GITHUB_ENV}"
- name: Docker login
shell: bash
run: |
set -euo pipefail
if [ -n "${REGISTRY_PASSWORD}" ]; then
: "${REGISTRY:?Set REGISTRY when REGISTRY_PASSWORD is configured}"
: "${REGISTRY_USER:?Set REGISTRY_USER secret when REGISTRY_PASSWORD is configured}"
echo "${REGISTRY_PASSWORD}" | docker login "${REGISTRY}" -u "${REGISTRY_USER}" --password-stdin
else
echo "REGISTRY_PASSWORD is not set; skipping docker login"
fi
- name: Build image
shell: bash
run: |
set -euo pipefail
docker build --pull -t "${IMAGE}" .
- name: Push image
shell: bash
run: |
set -euo pipefail
docker push "${IMAGE}"
if [ "${PUSH_LATEST:-false}" = "true" ]; then
latest_image="${IMAGE_REPO}:latest"
docker tag "${IMAGE}" "${latest_image}"
docker push "${latest_image}"
fi
- name: Configure remote Docker host
if: ${{ vars.DEPLOY_HOST != '' }}
shell: bash
run: |
set -euo pipefail
: "${DEPLOY_USER:?Set DEPLOY_USER when DEPLOY_HOST is configured}"
: "${DEPLOY_SSH_KEY:?Set DEPLOY_SSH_KEY secret when DEPLOY_HOST is configured}"
mkdir -p "${HOME}/.ssh"
chmod 700 "${HOME}/.ssh"
printf '%s\n' "${DEPLOY_SSH_KEY}" > "${HOME}/.ssh/id_deploy"
chmod 600 "${HOME}/.ssh/id_deploy"
ssh-keyscan -p "${DEPLOY_PORT}" "${DEPLOY_HOST}" >> "${HOME}/.ssh/known_hosts"
cat > "${HOME}/.ssh/config" <<EOF
Host swarm-manager
HostName ${DEPLOY_HOST}
Port ${DEPLOY_PORT}
User ${DEPLOY_USER}
IdentityFile ${HOME}/.ssh/id_deploy
IdentitiesOnly yes
EOF
echo "DOCKER_HOST=ssh://swarm-manager" >> "${GITHUB_ENV}"
- name: Deploy stack
shell: bash
run: |
set -euo pipefail
: "${DOMAIN:?Set DOMAIN in Gitea variables}"
docker stack deploy \
--with-registry-auth \
-c deploy/stack.example.yml \
"${STACK_NAME}"
+25
View File
@@ -0,0 +1,25 @@
FROM node:24-alpine AS builder
WORKDIR /app
ENV NEXT_TELEMETRY_DISABLED=1
COPY web/package.json web/package-lock.json ./
RUN npm ci --no-audit --fund=false
COPY web/ ./
RUN npm run build
FROM joseluisq/static-web-server:2-alpine
ENV SERVER_ROOT=/public \
SERVER_PORT=8080 \
SERVER_LOG_LEVEL=warn \
SERVER_DIRECTORY_LISTING=false \
SERVER_COMPRESSION=true
COPY --from=builder /app/out /public
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://127.0.0.1:8080/ >/dev/null || exit 1
+37
View File
@@ -18,3 +18,40 @@ node scripts/buildDatabase.mjs
npm run dev -- --port 3000
npm run build
```
Docker static deploy:
```bash
docker build -t progcode:static .
docker run --rm -p 8080:8080 progcode:static
```
Swarm example:
```bash
DOMAIN=progcode.example.com IMAGE=registry.example.com/progcode:static \
docker stack deploy -c deploy/stack.example.yml progcode
```
Gitea Actions variables:
- `DOMAIN` — public host routed by Traefik.
- `IMAGE` — full image tag to build and deploy, for example `registry.example.com/progcode:latest`.
- `IMAGE_REPO` — optional alternative to `IMAGE`; the workflow will tag images as `${IMAGE_REPO}:${short_sha}`.
- `REGISTRY` — registry host for `docker login`.
- `STACK_NAME` — optional, defaults to `progcode`.
- `TRAEFIK_NETWORK` — optional, defaults to `traefik-public`.
- `TRAEFIK_ENTRYPOINT` — optional, defaults to `websecure`.
- `PUSH_LATEST` — optional, set to `true` to also push `${IMAGE_REPO}:latest`.
Gitea Actions secrets:
- `REGISTRY_USER`
- `REGISTRY_PASSWORD`
- `DEPLOY_SSH_KEY` — only when deploying to a remote Swarm manager over SSH.
Remote Swarm deploy variables:
- `DEPLOY_HOST`
- `DEPLOY_USER`
- `DEPLOY_PORT` — optional, defaults to `22`.
+19
View File
@@ -0,0 +1,19 @@
services:
progcode:
image: ${IMAGE:-progcode:static}
networks:
- traefik-public
deploy:
replicas: 1
labels:
- traefik.enable=true
- traefik.docker.network=${TRAEFIK_NETWORK:-traefik-public}
- traefik.http.routers.progcode.rule=Host(`${DOMAIN}`)
- traefik.http.routers.progcode.entrypoints=${TRAEFIK_ENTRYPOINT:-websecure}
- traefik.http.routers.progcode.tls=true
- traefik.http.services.progcode.loadbalancer.server.port=8080
networks:
traefik-public:
external: true
name: ${TRAEFIK_NETWORK:-traefik-public}
+1 -1
View File
@@ -27,7 +27,7 @@ export default function HomePage() {
<div className="article-grid">
{articles.map((article) => (
<Link key={article.slug} className="article-card" href={`/articles/${encodeURIComponent(article.slug)}`}>
<Link key={article.slug} className="article-card" href={`/articles/${encodeURIComponent(article.slug)}/`}>
<img src={article.cover} alt="" />
<div className="article-card__body">
<div className="meta-line">
+2
View File
@@ -2,6 +2,8 @@ import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const nextConfig = {
output: 'export',
trailingSlash: true,
turbopack: {
root: dirname(fileURLToPath(import.meta.url)),
},