8.6 KiB
VPS Infrastructure Guide for AI Agents
Overview
This document describes the VPS infrastructure at 81.177.141.34 to help AI agents migrate or deploy new projects (e.g., voideaai.ru).
1. VPS Specifications
| Property | Value |
|---|---|
| IP | 81.177.141.34 |
| User | angel |
| Password | .-SHGWa_ |
| Sudo | passwordless via /etc/sudoers.d/angel |
| SSH key | added to ~/.ssh/authorized_keys |
| OS | Ubuntu 24.04 (Noble) |
| Kernel | OpenVZ/LXC container — limited kernel modules |
| Network interface | venet0 (no bridge NAT support) |
| Disk | 20GB total, ~13GB available |
| Docker | 29.5.0, iptables in nft mode (iptables-nft) |
2. Docker Constraints
Critical: network_mode: host only
Bridge networking (network_mode: bridge) does not work on this VPS. The OpenVZ/LXC kernel does not support masquerade for bridge interfaces through venet0. All containers must use:
network_mode: host
Docker daemon config (/etc/docker/daemon.json)
{"dns": ["8.8.8.8", "8.8.4.4"]}
Build constraint
All Docker builds require:
build:
network: host
Without this, apt-get and pip cannot download packages (no bridge NAT).
Docker Hub connectivity issues
Pulling large images (>100MB) from Docker Hub may time out (failed to copy: read tcp... connection timed out). Smaller images (nginx:alpine, postgres:16-alpine) work. Gitea and Portainer images cannot be pulled reliably.
Solutions attempted:
- Retry with
--platform linux/amd64 - Increase
max-download-attempts(not supported in Docker 29.5) - Clean build cache with
docker builder prune -af - All fail on large image layers
Workaround: Pull images locally and SCP (docker save → scp → docker load), or use a registry mirror.
3. Port Allocation (all on host)
| Port | Service | Container |
|---|---|---|
| 80 | nginx-proxy HTTP | nginx-proxy |
| 443 | nginx-proxy HTTPS | nginx-proxy |
| 8080 | PHP nginx (aegisone.ru) | aegisone-php-nginx |
| 9000 | PHP-FPM | aegisone-php-fpm |
| 8000 | FastAPI (service.aegisone.ru) | aegisone-app |
| 5432 | PostgreSQL (shared, aegisone) | aegisone-postgres |
| 5433 | PostgreSQL (Gitea) | gitea-db (planned) |
| 3000 | Gitea | gitea (planned) |
| 9001 | Portainer HTTP | portainer (planned) |
| 9443 | Portainer HTTPS | portainer (planned) |
Address resolution between containers
Since all containers use network_mode: host, they communicate via localhost:PORT. Docker DNS names do not resolve. In nginx.conf and app configs, use:
proxy_pass http://localhost:8080; # NOT container_name:port
4. Project Structure
/opt/projects/
nginx-proxy/ # Reverse proxy (nginx + certbot)
docker-compose.yml
nginx.conf
conf.d/ # Per-domain configs
certs/ # SSL certificates
html/ # Webroot for certbot
aegisone-php/ # PHP frontend (aegisone.ru)
docker-compose.yml
Dockerfile
nginx.conf # PHP nginx config (listen 8080)
src/ # PHP source files
aegisone-py/ # FastAPI backend (service.aegisone.ru)
docker-compose.yml
Dockerfile
app/ # Python source
sql/ # DB schema
gitea/ # Git hosting (planned)
docker-compose.yml
portainer/ # Docker UI (planned)
docker-compose.yml
5. nginx-proxy Configuration Pattern
Global config (nginx.conf)
resolver 127.0.0.11— not used in host mode, kept for compatibility- SSL termination handled by nginx-proxy
- Upstream protocols are HTTP (nginx-proxy terminates SSL and proxies via HTTP)
Conf.d file structure
Each domain has one .conf file with two server blocks:
# HTTP (no redirect — temporary, until LE certs)
server {
listen 80;
server_name example.ru;
location / {
proxy_pass http://localhost:PORT;
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;
}
}
# HTTPS (self-signed, for future LE)
server {
listen 443 ssl;
http2 on;
server_name example.ru;
ssl_certificate /etc/nginx/certs/live/aegisone.ru/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/aegisone.ru/privkey.pem;
# ... same location block
}
Note: HTTP→HTTPS redirects (return 301 https://...) caused redirect loops externally due to provider/proxy intercepting port 443. If you add them, test thoroughly.
SSL certificates
Currently using self-signed certs generated via:
sudo openssl req -x509 -nodes -days 30 -newkey rsa:2048 \
-keyout /opt/projects/nginx-proxy/certs/live/aegisone.ru/privkey.pem \
-out /opt/projects/nginx-proxy/certs/live/aegisone.ru/fullchain.pem \
-subj '/CN=aegisone.ru'
For Let's Encrypt:
cd /opt/projects/nginx-proxy
docker compose run --rm certbot certonly --webroot \
--webroot-path=/var/www/certbot \
-d example.ru \
--email admin@example.ru \
--agree-tos --non-interactive
6. Deployment Workflow
Initial setup
- Prepare files locally (this repo)
- Pack into tarball:
tar czf project.tar.gz . - SCP to VPS:
scp project.tar.gz angel@81.177.141.34:/home/angel/ - SSH and extract:
ssh angel@81.177.141.34 "tar xzf project.tar.gz -C /opt/projects/project-name/" - Build and start:
ssh angel@81.177.141.34 "cd /opt/projects/project-name && docker compose up -d --build"
Updating a running project
- Edit files locally
- SCP changed files to
/home/angel/on VPS - SSH and copy to correct location:
sudo cp /home/angel/file /opt/projects/project-name/ - Restart container:
docker compose -p project-name restart service-name
Copying files into a running container (alternative to rebuild)
docker cp /home/angel/file.py container-name:/app/path/file.py
docker compose -p project-name restart app
7. Database
PostgreSQL (aegisone shared DB)
- Host:
localhost:5432 - User:
aegisone - Password:
aegisone_pass - Database:
aegisone - Container:
aegisone-postgres(postgres:16-alpine)
Schema initialization
SQL files in /opt/projects/aegisone-py/sql/:
schema_postgresql.sql— full schemaseed_data.sql— seed data
Init scripts run only on first volume creation. To add missing tables later:
docker exec -i aegisone-postgres psql -U aegisone -d aegisone < /opt/projects/aegisone-py/sql/schema_postgresql.sql
Key schema pattern
- All tables use
BOOLEANforis_active(not integer) - Timestamps use
TIMESTAMP NOT NULL DEFAULT NOW() updated_attriggers viaupdate_updated_at_column()function
8. Useful Commands
# Check running containers
docker ps -a
# Check compose projects
docker compose ls
# View logs
docker logs container-name --tail 50
# Restart service
docker compose -p project-name restart service-name
# Rebuild and restart
docker compose -p project-name up -d --build
# Clean build cache
docker builder prune -af
# Test nginx config
docker compose -p nginx-proxy exec nginx nginx -t
# Reload nginx
docker compose -p nginx-proxy exec nginx nginx -s reload
# Check listening ports
ss -tlnp | grep -E ':(80|443|8080|8000|9000|5432)'
# Check disk
df -h /
docker system df
9. Adding a New Project (e.g., voideaai.ru)
Steps:
- Create directory:
mkdir -p /opt/projects/voideaai - Create
docker-compose.ymlwithnetwork_mode: hostandbuild.network: host - Create
Dockerfile(use--only-binary :all:for pip if avoiding apt) - Create nginx-proxy config:
nginx/conf.d/voideaai.conf(listen 80, proxy_pass localhost:PORT) - Generate cert if needed
- Restart nginx-proxy:
docker compose -p nginx-proxy restart nginx - Start project:
cd /opt/projects/voideaai && docker compose up -d --build
Port allocation for new project
Check existing ports first (ss -tlnp). Choose an unused port for the app and add it to the nginx-proxy config.
10. Known Issues
- Docker Hub timeouts on large images — consider pulling on local machine and transferring with
docker save/docker load - HTTPS redirect loops externally — port 443 may be intercepted by provider/proxy; test HTTP first
- Bridge networking doesn't work — all containers must use
network_mode: host - Docker build needs
network: host— without it, apt-get/pip fail - Config files with
$in content — when editing via SSH from PowerShell,$variablesget expanded; use SCP (write locally, then copy)