# 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: ```yaml network_mode: host ``` ### Docker daemon config (`/etc/docker/daemon.json`) ```json {"dns": ["8.8.8.8", "8.8.4.4"]} ``` ### Build constraint All Docker builds require: ```yaml 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: ```nginx 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: ```nginx # 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: ```bash 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: ```bash 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 1. Prepare files locally (this repo) 2. Pack into tarball: `tar czf project.tar.gz .` 3. SCP to VPS: `scp project.tar.gz angel@81.177.141.34:/home/angel/` 4. SSH and extract: `ssh angel@81.177.141.34 "tar xzf project.tar.gz -C /opt/projects/project-name/"` 5. Build and start: `ssh angel@81.177.141.34 "cd /opt/projects/project-name && docker compose up -d --build"` ### Updating a running project 1. Edit files locally 2. SCP changed files to `/home/angel/` on VPS 3. SSH and copy to correct location: `sudo cp /home/angel/file /opt/projects/project-name/` 4. Restart container: `docker compose -p project-name restart service-name` ### Copying files into a running container (alternative to rebuild) ```bash 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 schema - `seed_data.sql` — seed data Init scripts run only on first volume creation. To add missing tables later: ```bash docker exec -i aegisone-postgres psql -U aegisone -d aegisone < /opt/projects/aegisone-py/sql/schema_postgresql.sql ``` ### Key schema pattern - All tables use `BOOLEAN` for `is_active` (not integer) - Timestamps use `TIMESTAMP NOT NULL DEFAULT NOW()` - `updated_at` triggers via `update_updated_at_column()` function --- ## 8. Useful Commands ```bash # 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: 1. Create directory: `mkdir -p /opt/projects/voideaai` 2. Create `docker-compose.yml` with `network_mode: host` and `build.network: host` 3. Create `Dockerfile` (use `--only-binary :all:` for pip if avoiding apt) 4. Create nginx-proxy config: `nginx/conf.d/voideaai.conf` (listen 80, proxy_pass localhost:PORT) 5. Generate cert if needed 6. Restart nginx-proxy: `docker compose -p nginx-proxy restart nginx` 7. 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 1. **Docker Hub timeouts on large images** — consider pulling on local machine and transferring with `docker save`/`docker load` 2. **HTTPS redirect loops externally** — port 443 may be intercepted by provider/proxy; test HTTP first 3. **Bridge networking doesn't work** — all containers must use `network_mode: host` 4. **Docker build needs `network: host`** — without it, apt-get/pip fail 5. **Config files with `$` in content** — when editing via SSH from PowerShell, `$variables` get expanded; use SCP (write locally, then copy)