build: Dockerfile + nginx.conf + docker-compose + Gitea CI + 404 page
Some checks failed
Deploy to web.hhivp.com / deploy (push) Failing after 3s
Some checks failed
Deploy to web.hhivp.com / deploy (push) Failing after 3s
- Dockerfile multi-stage: node:22-alpine builds Astro → nginx:1.29-alpine отдаёт dist/. healthcheck wget --spider / - nginx.conf: gzip с подходящими типами (RSS/JSON/SVG/woff2), кэш /_astro/ immutable 1y, кэш css/js/img 30d, MIME application/rss+xml для feed, text/plain для robots/ai/llms/sitemap, кастомная 404 - docker-compose: контейнер anotherreflections-ru-v2 на 127.0.0.1:4084 (старый WP на :4080 остаётся для отката) - .gitea/workflows/deploy.yml: push в main → SSH-деплой на web, git fetch+reset → docker compose build → up -d → docker image prune (retention 7d по правилу проекта). Verify-шаг curl на :4084 - src/pages/404.astro — тематическая страница «не найдено» с навигацией
This commit is contained in:
42
.gitea/workflows/deploy.yml
Normal file
42
.gitea/workflows/deploy.yml
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
name: Deploy to web.hhivp.com
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up SSH key
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.SSH_PRIVATE_KEY }}" | base64 -d > ~/.ssh/deploy_key
|
||||||
|
chmod 600 ~/.ssh/deploy_key
|
||||||
|
cat >> ~/.ssh/config <<EOF
|
||||||
|
Host web
|
||||||
|
HostName ${{ secrets.SSH_HOST }}
|
||||||
|
User ${{ secrets.SSH_USER }}
|
||||||
|
Port ${{ secrets.SSH_PORT }}
|
||||||
|
IdentityFile ~/.ssh/deploy_key
|
||||||
|
StrictHostKeyChecking accept-new
|
||||||
|
UserKnownHostsFile ~/.ssh/known_hosts
|
||||||
|
EOF
|
||||||
|
|
||||||
|
- name: Deploy
|
||||||
|
run: |
|
||||||
|
ssh web 'bash -s' <<'REMOTE'
|
||||||
|
set -euo pipefail
|
||||||
|
cd /opt/docker/sites/anotherreflections-ru-v2
|
||||||
|
git fetch --prune origin
|
||||||
|
git reset --hard origin/main
|
||||||
|
docker compose build --pull
|
||||||
|
docker compose up -d
|
||||||
|
# Чистка старых образов (CI/CD retention >7d по правилу проекта)
|
||||||
|
docker image prune -af --filter "until=168h" >/dev/null 2>&1 || true
|
||||||
|
REMOTE
|
||||||
|
|
||||||
|
- name: Verify
|
||||||
|
run: |
|
||||||
|
ssh web 'curl -sf -H "Host: anotherreflections.ru" http://127.0.0.1:4084/ -o /dev/null -w "HTTP %{http_code} | %{size_download} bytes\n"'
|
||||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
|
# ─── Stage 1: build static site (Astro SSG) ────────────────────────────────
|
||||||
|
FROM node:22-alpine AS build
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# ─── Stage 2: nginx runtime ─────────────────────────────────────────────────
|
||||||
|
FROM nginx:1.29-alpine
|
||||||
|
|
||||||
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||||
|
CMD wget -q --spider http://127.0.0.1/ || exit 1
|
||||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
services:
|
||||||
|
anotherreflections-ru-v2:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: anotherreflections-ru-v2:latest
|
||||||
|
container_name: anotherreflections-ru-v2
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# На web.hhivp.com nginx-хост проксирует anotherreflections.ru
|
||||||
|
# на 127.0.0.1:4084. Менять при необходимости.
|
||||||
|
- "127.0.0.1:4084:80"
|
||||||
|
|
||||||
|
# Деплой:
|
||||||
|
# git pull в /opt/docker/sites/anotherreflections-ru-v2/
|
||||||
|
# docker compose build && docker compose up -d
|
||||||
|
# Откат — оставлен старый WP на 127.0.0.1:4080 (контейнер anotherreflections-ru),
|
||||||
|
# nginx vhost переключить обратно.
|
||||||
90
nginx.conf
Normal file
90
nginx.conf
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# nginx-конфиг внутри контейнера (статика Astro)
|
||||||
|
# TLS терминируется на хост-уровне (web.hhivp.com), здесь только HTTP.
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80 default_server;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
# Логи — на stdout/stderr контейнера (видны через docker logs)
|
||||||
|
access_log /dev/stdout;
|
||||||
|
error_log /dev/stderr warn;
|
||||||
|
|
||||||
|
# Безопасность: запрет на скрытые файлы (но разрешаем .well-known для LE)
|
||||||
|
location ~ /\.(?!well-known) {
|
||||||
|
deny all;
|
||||||
|
log_not_found off;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
# Сжатие
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 256;
|
||||||
|
gzip_proxied any;
|
||||||
|
gzip_comp_level 6;
|
||||||
|
gzip_types
|
||||||
|
text/plain text/css text/xml
|
||||||
|
application/json application/xml application/rss+xml application/atom+xml
|
||||||
|
application/javascript application/x-javascript
|
||||||
|
image/svg+xml font/woff2;
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
# MIME для RSS-фидов и текстовых служебных файлов
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
location = /feed.xml {
|
||||||
|
default_type application/rss+xml;
|
||||||
|
charset utf-8;
|
||||||
|
try_files /feed.xml =404;
|
||||||
|
expires 5m;
|
||||||
|
add_header Cache-Control "public, max-age=300, must-revalidate";
|
||||||
|
}
|
||||||
|
location ~ ^/category/[^/]+/feed\.xml$ {
|
||||||
|
default_type application/rss+xml;
|
||||||
|
charset utf-8;
|
||||||
|
expires 5m;
|
||||||
|
add_header Cache-Control "public, max-age=300, must-revalidate";
|
||||||
|
}
|
||||||
|
location = /sitemap.txt {
|
||||||
|
default_type text/plain;
|
||||||
|
charset utf-8;
|
||||||
|
}
|
||||||
|
location = /robots.txt { default_type text/plain; charset utf-8; }
|
||||||
|
location = /ai.txt { default_type text/plain; charset utf-8; }
|
||||||
|
location = /llms.txt { default_type text/plain; charset utf-8; }
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
# Кэш статики (хеш у Astro в имени файла)
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
location /_astro/ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable, max-age=31536000";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
location ~* \.(?:css|js|woff2?|ttf|otf|eot)$ {
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, max-age=2592000";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
location ~* \.(?:png|jpe?g|gif|svg|webp|avif|ico)$ {
|
||||||
|
expires 30d;
|
||||||
|
add_header Cache-Control "public, max-age=2592000";
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
# Маршрутизация: trailingSlash: 'always' уже встроен в Astro,
|
||||||
|
# все страницы экспортированы как dir/index.html
|
||||||
|
# ────────────────────────────────────────────────────────────────
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ $uri.html =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Кастомная 404
|
||||||
|
error_page 404 /404.html;
|
||||||
|
location = /404.html { internal; }
|
||||||
|
}
|
||||||
24
src/pages/404.astro
Normal file
24
src/pages/404.astro
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
---
|
||||||
|
<BaseLayout title="Страница не найдена" description="404 — такой страницы здесь нет.">
|
||||||
|
<section class="hero" style="padding: 4.5rem 1rem 2.5rem;">
|
||||||
|
<span class="hero-eyebrow">404</span>
|
||||||
|
<h1 style="font-size: clamp(3rem, 8vw, 5rem); line-height: 1;">Не найдено</h1>
|
||||||
|
<p class="hero-tagline">
|
||||||
|
Возможно, страница переехала вместе с обновлением сайта, либо её никогда здесь не было — иные отражения причудливо тасуют реальность.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section style="text-align: center; margin: 2rem 0 4rem;">
|
||||||
|
<p style="color: var(--fg-muted); margin-bottom: 1.5rem;">
|
||||||
|
Попробуйте начать с одной из этих точек входа:
|
||||||
|
</p>
|
||||||
|
<p style="display: flex; gap: .8rem; flex-wrap: wrap; justify-content: center;">
|
||||||
|
<a class="cookie-btn cookie-btn-primary" href="/">К ленте новостей</a>
|
||||||
|
<a class="cookie-btn" href="/miry/">Миры</a>
|
||||||
|
<a class="cookie-btn" href="/o-nas/">О нас</a>
|
||||||
|
<a class="cookie-btn" href="/kontakty/">Контакты</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</BaseLayout>
|
||||||
Reference in New Issue
Block a user