# FastAPI backend. PublishPort binds to 127.0.0.1 only: the intent is a reverse proxy # (deploy/nginx/stocklab.conf) terminating TLS, not direct public exposure. [Unit] Description=StockLab backend API PartOf=stocklab.target Wants=network-online.target After=network-online.target # systemd ordering only guarantees START order, not READINESS. The container's own HealthCmd and # the app's retry-on-connect behaviour are what actually handle "Postgres is up but not accepting # connections yet" — see docs/AUDIT_DEPLOYMENT_E.md, this is a real difference from compose's # `depends_on: condition: service_healthy`. After=stocklab-db.service stocklab-redis.service Requires=stocklab-db.service stocklab-redis.service StartLimitBurst=5 StartLimitIntervalSec=300 [Container] ContainerName=stocklab-backend Image=localhost/stocklab-backend:screener-percent-final-20260909-194639 Network=host EnvironmentFile=/etc/stocklab/stocklab.env Environment=ENVIRONMENT=production # /ready (checks DB connectivity), not /health (process-up only): a backend that is running but # cannot reach its database must not be reported healthy. DropCapability=ALL NoNewPrivileges=true ReadOnly=true # The app writes nothing to disk except temp files; a writable /tmp keeps ReadOnly=true workable. Tmpfs=/tmp:rw,noexec,nosuid,size=64m Exec=uvicorn app.main:app --host 127.0.0.1 --port 18000 HealthCmd=python3 -c 'import urllib.request; urllib.request.urlopen("http://127.0.0.1:18000/ready", timeout=3)' HealthInterval=30s HealthTimeout=10s HealthRetries=5 HealthStartPeriod=30s [Service] # AUDIT FIX (final master pass, §84 "Не допускай uncontrolled resource growth"). # No container had ANY limit: a runaway worker could take the whole machine, and §81 requires # StockLab not to damage the other services already running on this production host. # These are STARTING POINTS sized for a small single-server deployment, not measurements — nothing # here has been profiled, because nothing here has ever run. Raise them from observed usage rather # than trusting the numbers. # IMPORTANT for rootless: a cgroup v2 host that has not DELEGATED the memory/cpu controllers to the # user slice accepts these silently and ignores them. `./scripts/stocklab-doctor.sh --rootless # check-cgroups` reports which controllers are actually delegated — check it before believing a # limit is in force. MemoryMax=1G MemorySwapMax=0 CPUQuota=100% TasksMax=512 Restart=always # Bounded restart storm: 5 attempts in 5 minutes, then stop trying and stay failed so # `systemctl status stocklab` shows a real failure instead of an endless loop. [Install] # §78: `systemctl start stocklab` starts this container, and # `systemctl stop stocklab` stops it. WantedBy pulls it in with the target; # PartOf propagates stop/restart down from the target. WantedBy=multi-user.target stocklab.target