improvement: docker infrastructure overhauled.

This commit is contained in:
2026-05-03 20:15:39 +03:30
parent a05249e343
commit 5682da87aa
20 changed files with 542 additions and 172 deletions

View File

@@ -19,4 +19,7 @@ DOMAIN=dyolink.com
# Frontend Environment (create frontend.env from this)
# NEXT_PUBLIC_API_URL=/api
# NEXT_PUBLIC_APP_NAME=Dyolink
# NEXT_PUBLIC_APP_URL=https://dyolink.com
# NEXT_PUBLIC_APP_URL=https://dyolink.com
# --- Staging on your server (docker-compose.staging.yml) ---
# See env.staging.example, database.staging.env.example, backend.staging.env.example

View File

@@ -0,0 +1,12 @@
# Copy to backend.staging.env — DATABASE_URL must match database.staging.env credentials.
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://postgres:changeme_staging_strong_password@postgres:5432/dyolink_db
JWT_SECRET=replace_with_a_long_random_secret
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=another_long_random_secret_different_from_JWT_SECRET
JWT_REFRESH_EXPIRES_IN=30d
FRONTEND_URL=http://178.131.50.201:8088

View File

@@ -0,0 +1,4 @@
# Copy to database.staging.env (do not commit real passwords).
POSTGRES_USER=postgres
POSTGRES_PASSWORD=changeme_staging_strong_password
POSTGRES_DB=dyolink_db

View File

@@ -0,0 +1,19 @@
# Template for manual pull-only deploy (when not using CI-generated deploy.registry.env).
# CI workflow generates this file automatically; you normally only need secrets on disk.
#
# docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d
# --- Registry boundary (swap when moving Gitea → Docker Hub) ---
# Gitea: REGISTRY_PREFIX = <host>:<port>/<owner>
# Hub: REGISTRY_PREFIX = docker.io/<user> (or your username for implicit hub)
REGISTRY_PREFIX=178.131.50.201:3000/yourgiteauser
# Short git SHA from CI, or "latest" after a manual pull of :latest
IMAGE_TAG=latest
# Host port published for nginx (URL = http://<your-ip>:<this-port>)
STAGING_HTTP_PORT=8088
# Absolute path on the server where database.staging.env and backend.staging.env live.
# Use forward slashes on Windows. Same variable as Gitea Actions → DEPLOY_SECRETS_DIR.
# DEPLOY_SECRETS_DIR=D:/dyolink/secrets

View File

@@ -53,7 +53,7 @@ services:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/api/health', (r) => {if(r.statusCode!==200)process.exit(1)})"]
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3
@@ -81,7 +81,7 @@ services:
max-size: "10m"
max-file: "3"
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000', (r) => {if(r.statusCode!==200)process.exit(1)})"]
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3

View File

@@ -0,0 +1,105 @@
# Pull-only staging stack — uses images from a registry (Gitea Packages / Docker Hub / etc.).
# No backend/frontend source on the deployment host except this compose file + config + secrets.
#
# Required env (see deploy.registry.env.example):
# REGISTRY_PREFIX e.g. 178.131.50.201:3000/yourgiteauser (no protocol, no trailing slash)
# IMAGE_TAG short sha or "latest" (CI sets this per deploy)
# Optional:
# DEPLOY_SECRETS_DIR absolute path on the server to database/backend *.env files (see below)
#
# Deploy:
# docker compose -f docker-compose.registry.yml --env-file deploy.registry.env pull
# docker compose -f docker-compose.registry.yml --env-file deploy.registry.env up -d
name: dyolink-registry
services:
postgres:
image: postgres:15-alpine
container_name: dyolink_postgres_staging
env_file:
- ${DEPLOY_SECRETS_DIR:-.}/database.staging.env
environment:
TZ: UTC
volumes:
- postgres_data_staging:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- ./database/backups:/backups
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 15s
timeout: 10s
retries: 5
start_period: 40s
backend:
image: ${REGISTRY_PREFIX}/dyolink-backend:${IMAGE_TAG:-latest}
container_name: dyolink_backend_staging
depends_on:
postgres:
condition: service_healthy
env_file:
- ${DEPLOY_SECRETS_DIR:-.}/backend.staging.env
environment:
NODE_ENV: production
TZ: UTC
PORT: "3000"
expose:
- "3000"
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
frontend:
image: ${REGISTRY_PREFIX}/dyolink-frontend:${IMAGE_TAG:-latest}
container_name: dyolink_frontend_staging
depends_on:
- backend
environment:
NODE_ENV: production
TZ: UTC
PORT: "3000"
HOSTNAME: "0.0.0.0"
expose:
- "3000"
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
nginx:
image: nginx:alpine
container_name: dyolink_nginx_staging
depends_on:
- backend
- frontend
ports:
- "${STAGING_HTTP_PORT:-8088}:80"
volumes:
- ./nginx/http-only.conf:/etc/nginx/conf.d/default.conf:ro
- ./logs/nginx-staging:/var/log/nginx
networks:
- dyolink_staging
restart: unless-stopped
networks:
dyolink_staging:
name: dyolink_staging
volumes:
postgres_data_staging:
name: dyolink_postgres_data_staging

View File

@@ -0,0 +1,107 @@
# Staging stack — builds images from local backend/frontend (needs full repo clone).
# For pull-only images + registry (no app source on server), use docker-compose.registry.yml
# and .gitea/workflows/registry-build-deploy.yml instead.
#
# From this directory:
# docker compose -f docker-compose.staging.yml --env-file .env.staging up -d --build
name: dyolink-staging
services:
postgres:
image: postgres:15-alpine
container_name: dyolink_postgres_staging
env_file:
- database.staging.env
environment:
TZ: UTC
volumes:
- postgres_data_staging:/var/lib/postgresql/data
- ./database/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- ./database/backups:/backups
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 15s
timeout: 10s
retries: 5
start_period: 40s
backend:
build:
context: ../backend
dockerfile: Dockerfile
container_name: dyolink_backend_staging
depends_on:
postgres:
condition: service_healthy
env_file:
- backend.staging.env
environment:
NODE_ENV: production
TZ: UTC
PORT: "3000"
expose:
- "3000"
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/api/health', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
frontend:
build:
context: ../frontend
dockerfile: Dockerfile
args:
NEXT_PUBLIC_API_URL: ${STAGING_NEXT_PUBLIC_API_URL:-http://178.131.50.201:8088/api}
NEXT_PUBLIC_APP_URL: ${STAGING_NEXT_PUBLIC_APP_URL:-http://178.131.50.201:8088}
NEXT_PUBLIC_APP_NAME: ${STAGING_NEXT_PUBLIC_APP_NAME:-Dyolink}
container_name: dyolink_frontend_staging
depends_on:
- backend
environment:
NODE_ENV: production
TZ: UTC
PORT: "3000"
HOSTNAME: "0.0.0.0"
expose:
- "3000"
networks:
- dyolink_staging
restart: unless-stopped
healthcheck:
test: ["CMD", "node", "-e", "require('http').get('http://127.0.0.1:3000/', (r) => {if(r.statusCode!==200)process.exit(1)})"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
nginx:
image: nginx:alpine
container_name: dyolink_nginx_staging
depends_on:
- backend
- frontend
ports:
- "${STAGING_HTTP_PORT:-8088}:80"
volumes:
- ./nginx/http-only.conf:/etc/nginx/conf.d/default.conf:ro
- ./logs/nginx-staging:/var/log/nginx
networks:
- dyolink_staging
restart: unless-stopped
networks:
dyolink_staging:
name: dyolink_staging
volumes:
postgres_data_staging:
name: dyolink_postgres_data_staging

View File

@@ -1,11 +1,14 @@
# Copy .env.docker.example to .env.docker and adjust (optional).
# Defaults below are for local development only.
services:
postgres:
image: postgres:15-alpine
container_name: dyolink_db_container
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: 1234
POSTGRES_DB: dyolink_db
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dyolink_dev_change_me}
POSTGRES_DB: ${POSTGRES_DB:-dyolink_db}
ports:
- "5433:5432"
volumes:
@@ -16,7 +19,7 @@ services:
- dyolink_network
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres}"]
interval: 10s
timeout: 5s
retries: 5
@@ -30,7 +33,7 @@ services:
env_file:
- ../backend/.env
environment:
- DATABASE_URL=postgresql://postgres:1234@postgres:5432/dyolink_db
- DATABASE_URL=postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-dyolink_dev_change_me}@postgres:5432/${POSTGRES_DB:-dyolink_db}
- FRONTEND_URL=http://frontend:3000
- PORT=3000
ports:
@@ -73,10 +76,7 @@ services:
- frontend
ports:
- "8080:80"
- "8443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
- ./ssl:/etc/nginx/ssl:ro
- ./logs/nginx:/var/log/nginx
networks:
- dyolink_network

View File

@@ -0,0 +1,6 @@
# Optional: save as .env next to infrastructure/docker-compose.yml
# Docker Compose reads this file automatically for variable substitution.
POSTGRES_USER=postgres
POSTGRES_PASSWORD=dyolink_dev_change_me
POSTGRES_DB=dyolink_db

View File

@@ -0,0 +1,13 @@
# Copy to .env.staging next to docker-compose.staging.yml (optional).
# Used only for compose variable substitution (build args, host port).
STAGING_HTTP_PORT=8088
# Public URLs baked into the frontend image at build time — must match how users open the app.
STAGING_NEXT_PUBLIC_API_URL=http://178.131.50.201:8088/api
STAGING_NEXT_PUBLIC_APP_URL=http://178.131.50.201:8088
STAGING_NEXT_PUBLIC_APP_NAME=Dyolink
# Change the IP/port if your server address differs.
# Registry / pull-only deploy (see deploy.registry.env.example + docker-compose.registry.yml).

View File

@@ -1,19 +1,13 @@
FROM nginx:alpine
# Remove default configuration
RUN rm /etc/nginx/conf.d/default.conf
RUN rm -f /etc/nginx/conf.d/default.conf
# Copy custom configuration
COPY nginx.conf /etc/nginx/conf.d/
COPY http-only.conf /etc/nginx/conf.d/default.conf
# Create log directory
RUN mkdir -p /var/log/nginx && \
chown -R nginx:nginx /var/log/nginx && \
chmod -R 755 /var/log/nginx
# Switch to non-root user
USER nginx
EXPOSE 80
EXPOSE 80 443
CMD ["nginx", "-g", "daemon off;"]
CMD ["nginx", "-g", "daemon off;"]

View File

@@ -0,0 +1,54 @@
# HTTP only — local dev and IP-based staging (no TLS).
# Use with: docker compose and map host port e.g. 8080:80 or 8088:80
upstream dyolink_backend {
server backend:3000;
keepalive 32;
}
upstream dyolink_frontend {
server frontend:3000;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name _;
client_max_body_size 50M;
location / {
proxy_pass http://dyolink_frontend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 300;
}
location /api {
proxy_pass http://dyolink_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 300;
proxy_connect_timeout 300;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}