DEV Community

weiwuji
weiwuji

Posted on

One-Person Company Infrastructure: The $350/Year Stack Behind a Six-Figure Solo Business

One-Person Company Infrastructure: The $350/Year Stack Behind a Six-Figure Solo Business

The Pain — You assume a one-person company needs expensive servers, domains, OSS buckets, and databases to start — and that picking the "right" cloud-native stack is the real engineering challenge.

What You'll Learn:

  • The real infrastructure bill behind a solo business earning ¥500K/year (~$70K) — ¥2,500/year total (~$350)
  • The "iron triangle" stack (FastAPI + Celery + SQLAlchemy on a single VPS) and why one box beats microservices
  • Production Nginx configuration: SSL, rate limiting, WebSocket upgrade, static caching
  • Docker Compose + Portainer deployment, a GitHub Actions CI/CD pipeline, and a ¥0 monitoring trio
  • A simple rule for knowing when to scale — and when to leave your infrastructure alone

Last week a reader messaged me: "Wu, I want to run a one-person company too, but a server alone runs into the thousands — then a domain, OSS, a database... isn't the startup cost huge?"

My reply was blunt: your biggest cost isn't the server. It's the time you burn on the wrong tech stack.

I've been running MoLi AGI — an OPC (one-person company) — for nearly six months now. On top of it I've built a complete automated business: daily WeChat publishing, an AI Agent service, SaaS mini-tools, and a data dashboard — all in production. Today I'm laying out the entire stack, real bills included, so you can see exactly what infrastructure a ¥500K-a-year solo business actually needs.

The Iron Triangle: Compute, Data, Deliver — ~$350/year
The Iron Triangle: Compute, Data, Deliver — ~$350/year

0. Environment Dependencies

Before we start, here's the core dependency list running on my server.

Python environment (requirements.txt):

fastapi==0.115.0
uvicorn[standard]==0.30.0
gunicorn==22.0.0
celery[redis]==5.4.0
sqlalchemy==2.0.35
pymysql==1.1.1
redis==5.2.0
minio==7.2.10
boto3==1.35.0
python-dotenv==1.0.1
httpx==0.27.0
Enter fullscreen mode Exit fullscreen mode

System dependencies (Docker environment):

# Lightweight server with Docker + Docker Compose installed
docker --version   # >= 24.0
docker compose version  # >= 2.20

# One-line install script (Ubuntu 22.04/24.04)
curl -fsSL https://get.docker.com | bash
apt-get install -y docker-compose-plugin
Enter fullscreen mode Exit fullscreen mode

Verify everything is ready:

# Verify Python packages
python -c "import fastapi, celery, sqlalchemy, redis, minio; print('OK')"

# Verify Docker service
docker run hello-world

# Verify ports are not occupied
ss -tlnp | grep -E '8000|443|80|3306|6379|9000'
Enter fullscreen mode Exit fullscreen mode

1. The Tech Stack Panorama: My "Iron Triangle"

Here's the whole architecture. What does a one-person company fear most? Going big and broad. You cannot manage a Kubernetes cluster, write business code, and run ops monitoring all by yourself. The architecture must be minimal, maintainable, and operable by a single person.

┌───────────────────────────────────────────────────────┐
│                   User Traffic Entry                  │
│       ┌───────────┐ ┌───────────┐ ┌───────────┐       │
│       │ WeChat/H5 │ │  MiniApp  │ │   API GW  │       │
│       └─────┬─────┘ └─────┬─────┘ └─────┬─────┘       │
└────────┼─────────────┼─────────────┼──────────────────┘
│        │             │             │                  │
┌────────▼─────────────▼─────────────▼──────────────────┐
│            Business Layer (single process)            │
│       ┌───────────────────────────────────────┐       │
│       │     FastAPI + Celery + SQLAlchemy     │       │
│       │   One process: API / workers / cron   │       │
│       └───────────────────┬───────────────────┘       │
└───────────────────────────┼───────────────────────────┘
│                           │                           │
┌───────────────────────────▼───────────────────────────┐
│              Data & Infrastructure Layer              │
│   ┌────────┐  ┌────────┐  ┌────────┐  ┌────────────┐  │
│   │ MySQL  │  │ Redis  │  │ MinIO  │  │ Aliyun OSS │  │
│   └────────┘  └────────┘  └────────┘  └────────────┘  │
│  ┌────────┐  ┌──────────────┐  ┌──────────────┐       │
│  │   ES   │  │ GitHub/Gitee │  │ Docker Swarm │       │
│  └────────┘  └──────────────┘  └──────────────┘       │
└───────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

My core principles are only three:

  1. Merge, don't split — if a single VPS can run it, don't reach for microservices.
  2. SaaS over self-hosted — push notifications, email, monitoring: use third parties whenever possible.
  3. Containerize everything — the Docker toolchain makes migration cost close to zero.

2. Server Choice: A Lightweight VPS Is the Sweet Spot for an OPC

Let's answer the title question directly: what server does a ¥500K/year business need?

One 2-core 4GB lightweight application server, under ¥100/month.

I currently run Alibaba Cloud's "Lightweight Application Server" on the Hong Kong node: 2 cores, 4GB RAM, 40GB SSD, 3Mbps bandwidth — about ¥84/month on annual billing. What runs on it?

  • Main API (FastAPI + Gunicorn + Uvicorn)
  • Celery Worker + Beat (scheduled jobs)
  • Redis (cache + message queue)
  • MySQL 8.0 (dev environment only — RDS lives elsewhere)
  • MinIO (object storage)
  • Nginx (reverse proxy + SSL)
  • Portainer (container management panel)

One machine, seven services, all Dockerized. Peak memory usage: 3.2GB. Average CPU load: 0.8. And there's still headroom for two more small services.

Why not the full cloud-native stack? I tried it. In the first week on EKS I burned ¥200 on the NAT Gateway alone — with 37 daily active users.

The golden rule of OPC: don't touch Kubernetes before you have 10,000 users.

For a single node, Docker Compose + Portainer is more than enough. If you ever need to scale horizontally, Swarm mode makes the migration trivial — change the deploy block in compose.yml and you go from single box to cluster.

Here's the core of my docker-compose.yml:

version: '3.8'

services:
  api:
    build: ./app
    environment:
      - DATABASE_URL=mysql+pymysql://user:***@db:3306/opc
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    volumes:
      - ./app:/app
    ports:
      - "8000:8000"
    restart: unless-stopped

  celery:
    build: ./app
    command: celery -A tasks worker -l info
    depends_on:
      - redis
      - db

  redis:
    image: redis:7-alpine
    restart: unless-stopped

  db:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    restart: unless-stopped

volumes:
  mysql_data:
Enter fullscreen mode Exit fullscreen mode

The biggest win of containerization isn't performance — it's the drop in cognitive load. Moving machines is a single docker compose up -d; everything is back up in a minute.

3. Reverse Proxy & SSL: Complete Nginx Configuration

Cost: 5 Tools vs 1 System
Cost: 5 Tools vs 1 System

Too many OPC APIs run naked on the public internet: no HTTPS, no rate limiting, no anti-scraping — the front door wide open. Below is the Nginx config I run in production, covering SSL (Let's Encrypt with auto-renewal), rate limiting, WebSocket upgrade, and static asset caching.

3.1 Main Nginx config

# /etc/nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    server_tokens off;  # hide the version number

    # log format (includes request time — handy for hunting slow endpoints)
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'rt=$request_time uct="$upstream_connect_time" '
                    'uht="$upstream_header_time" urt="$upstream_response_time"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    keepalive_timeout 65;

    # Gzip compression
    gzip on;
    gzip_min_length 1k;
    gzip_types text/plain application/json application/javascript text/css;

    # Rate limit zone: max 30 requests/sec per IP (API protection)
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=30r/s;
    # Connection limit: max 20 concurrent connections per IP
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    include /etc/nginx/conf.d/*.conf;
}
Enter fullscreen mode Exit fullscreen mode

3.2 Site config (with SSL)

# /etc/nginx/conf.d/opc.conf
# force HTTP → HTTPS redirect
server {
    listen 80;
    server_name api.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name api.yourdomain.com;

    # === SSL certificate (Let's Encrypt) ===
    ssl_certificate     /etc/letsencrypt/live/api.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.yourdomain.com/privkey.pem;

    # === SSL hardening ===
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    # === Security headers ===
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Strict-Transport-Security "max-age=63072000" always;

    # === Rate limiting (protect the backend) ===
    limit_req zone=api_limit burst=20 nodelay;
    limit_conn conn_limit 20;

    # max client request body
    client_max_body_size 10m;

    # === Static assets (served by Nginx directly, no backend round-trip) ===
    location /static/ {
        alias /opt/opc/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # === API reverse proxy ===
    location / {
        proxy_pass http://127.0.0.1:8000;
        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;

        # timeouts
        proxy_connect_timeout 30s;
        proxy_read_timeout 60s;
        proxy_send_timeout 30s;

        # buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 32k;
    }

    # === WebSocket upgrade (needed for long-lived AI Agent connections) ===
    location /ws/ {
        proxy_pass http://127.0.0.1:8000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;
    }
}
Enter fullscreen mode Exit fullscreen mode

3.3 SSL certificate auto-renewal

# install certbot
apt-get install -y certbot python3-certbot-nginx

# first issuance (make sure the domain resolves to your server first)
certbot --nginx -d api.yourdomain.com -d www.yourdomain.com

# cron auto-renewal (1st of every month at 3am)
echo "0 3 1 * * certbot renew --quiet --post-hook 'nginx -s reload'" | crontab -

# verify auto-renewal (dry run)
certbot renew --dry-run
Enter fullscreen mode Exit fullscreen mode

3.4 Verifying Nginx + SSL

# 1. check config syntax
nginx -t

# 2. reload config
nginx -s reload

# 3. test HTTPS access
curl -I https://api.yourdomain.com

# 4. check SSL certificate validity
echo | openssl s_client -servername api.yourdomain.com -connect api.yourdomain.com:443 2>/dev/null \
  | openssl x509 -noout -dates

# 5. SSL Labs online check (aim for an A+ rating)
# open in browser: https://www.ssllabs.com/ssltest/analyze.html?d=api.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

4. Database & Storage: Layering Is Just a Way to Save Money

4.1 Relational database

Primary: Alibaba Cloud RDS MySQL, 2 cores 4GB, 50GB SSD — ¥98/month (pay-as-you-go, no traffic fee).

Why not self-host MySQL? Because self-hosting means writing backup scripts, expanding the disk yourself, and recovering from crashes yourself. An OPC's time is income — spending it on ops is worse than spending it on code. RDS gives you automatic backups, one-click rollback, and slow-query logs. A 15-minute look every six months is all it takes.

4.2 Cache layer

Redis 7-alpine runs directly on the lightweight server — the Docker image is only 32MB. In production I gave it a 512MB memory limit; it actually uses under 100MB.

Use cases:

  • Session storage
  • Celery result backend
  • API response cache (TTL 60s, ~65% hit rate)
  • Simple counters (DAU, API call volume)

4.3 File storage

The object storage trio: OSS (primary) + MinIO (local cache) + CDN.

Alibaba Cloud OSS (standard tier): 50GB capacity + 10GB monthly traffic ≈ ¥15/month. What goes in it?

  • User-uploaded images and PDFs
  • Generated article cover images
  • Intermediate files produced by AI Agents

MinIO acts as a local hot cache — frequently accessed files are pulled from OSS to the local box to cut egress fees. A simple LRU eviction strategy:

import os
from pathlib import Path
from functools import lru_cache
from minio import Minio  # <- fix: import the MinIO client


class FileCache:
    def __init__(
        self,
        local_dir: str = "/data/cache",
        max_size_mb: int = 1024,
        # fix: initialize the MinIO client (missing in the original draft,
        # which left self.client undefined)
        oss_endpoint: str = "oss-cn-hongkong.aliyuncs.com",
        oss_access_key: str = "",
        oss_secret_key: str = "",
        oss_bucket: str = "opc-files",
    ):
        self.local_dir = Path(local_dir)
        self.local_dir.mkdir(parents=True, exist_ok=True)
        self.max_size = max_size_mb * 1024 * 1024

        # ★ Bug fix: the original code never initialized the MinIO client
        self.client = Minio(
            oss_endpoint,
            access_key=oss_access_key,
            secret_key=oss_secret_key,
            secure=True,
        )
        self.oss_bucket = oss_bucket

    @lru_cache(maxsize=128)
    def get(self, key: str) -> bytes:
        local_path = self.local_dir / key
        if local_path.exists():
            return local_path.read_bytes()
        # pull from OSS (self.client is now properly defined)
        data = self.client.get_object(self.oss_bucket, key).read()
        local_path.write_bytes(data)
        self._evict_if_needed()
        return data

    def _evict_if_needed(self):
        total = sum(
            f.stat().st_size for f in self.local_dir.rglob("*") if f.is_file()
        )
        if total > self.max_size:
            files = sorted(self.local_dir.rglob("*"), key=os.path.getatime)
            for f in files[:10]:
                f.unlink()
Enter fullscreen mode Exit fullscreen mode

Bug note: the original draft called self.client.get_object(...) without ever initializing self.client, which raised AttributeError: 'FileCache' object has no attribute 'client' at runtime. Fixed above.

CDN origin-pull traffic costs about ¥3–5 per month. Absurdly cheap.

5. CI/CD & Deployment: GitHub Actions Instead of Jenkins

One person does not need Jenkins. The GitHub Actions free tier is more than enough for an OPC — I can't use it all up.

My deployment pipeline:

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]
jobs:
  test-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: |
          docker compose -f docker-compose.test.yml up --abort-on-container-exit
      - name: Build and push
        env:
          DOCKER_BUILDKIT: 1
        run: |
          docker build -t opc-app:${{ github.sha }} .
          docker save opc-app:${{ github.sha }} | gzip > app.tar.gz
      - name: Deploy to VPS
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_KEY }}
          source: "app.tar.gz"
          target: "/opt/opc/deploy/"
      - name: Restart services
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_KEY }}
          script: |
            cd /opt/opc
            docker compose pull
            docker compose up -d --force-recreate api
            docker image prune -f
Enter fullscreen mode Exit fullscreen mode

The whole flow: git push → GitHub Actions runs tests → builds the image → SCPs it to the server → docker compose hot-reloads. From push to live: 47 seconds on average. Nobody needs to watch it.

6. What About Monitoring?

Three tools, all free:

Tool Purpose Free tier
Sentry Error tracking 5,000 events/month — more than a solo project can use
UptimeRobot Availability monitoring 5 URLs free, probed every 5 minutes; email + SMS alerts on downtime
Healthchecks.io Cron job monitoring Celery Beat pings after every run; alert if the ping times out

Combined monthly cost of all three: ¥0.

7. The Annual Bill: Every Yuan Disclosed

A lot of people assume building a tech product must burn money. Here's my actual bill (annual pricing, CNY):

Item Spec Year Month
Lightweight server 2C4G, 40GB SSD ¥1,008 ¥84
RDS MySQL 2C4G, 50GB SSD ¥1,176 ¥98
Alibaba Cloud OSS 50GB + CDN traffic ¥180 ¥15
Domain 1 × .com + 1 × .cn ¥136 ¥11
Enterprise email Alibaba free tier ¥0 ¥0
SSL certificate Let's Encrypt (auto-renew) ¥0 ¥0
GitHub Pro Personal ¥0 ¥0
Sentry Free tier ¥0 ¥0
Monitoring tools UptimeRobot + Healthchecks ¥0 ¥0
Total ¥2,500 ¥208

¥2,500 a year (~$350). That is the entire infrastructure cost of running MoLi AGI.

For a brand-new OPC, you can go even cheaper:

  • Squeeze every service onto one lightweight server (¥1,008/year is enough)
  • Replace MySQL with SQLite (¥0)
  • Self-host object storage with MinIO (mind the disk filling up)

Extreme minimum: ¥1,008/year — ¥84/month.

8. Why I Chose the "Old-School" Stack

You may have noticed: no Serverless (Lambda/FaaS), no NoSQL (MongoDB is fine — I just don't need it), no message queue (beyond Redis's built-in pub/sub).

The reason is simple: a one-person company's stack must serve "fast delivery," not "architectural aesthetics."

Can this stack survive growth from 1,000 to 10,000 users?

  • API: add an Nginx cache layer
  • Database: add a read replica
  • Server: upgrade to 4 cores 8GB

How long does that take? One weekend. And before that weekend ever arrives, I've already run a full year at ¥2,500 and earned more than 10× that back.

OPC tech selection is not an engineering problem — it's a finance problem. Is every minute of server time making you money, or just satisfying your "technical hygiene"?

When should you upgrade? Here's a simple decision rule:

  • If server cost is below 1% of your monthly income, don't touch the infrastructure.
  • If ops time exceeds coding time, upgrade immediately.

I tested this rule for three months: ops work averaged under 30 minutes per week (mostly glancing at monitoring dashboards and deploying), and server cost came to 0.2% of monthly income. So far, nothing has moved.

9. Action Checklist

If you want to start an OPC too, here's what you can do today:

  1. Buy a lightweight server (Alibaba Cloud / Tencent Cloud / Huawei Cloud — whichever is cheapest), 2C2G minimum, ¥600–800/year
  2. Register a .com domain (~¥55/year)
  3. Install Docker + Docker Compose (5 minutes)
  4. Manage code on GitHub and configure a GitHub Actions auto-deploy
  5. Deploy your first API — even a Hello World — and complete the push-to-live loop
  6. Grab my Docker Compose starter template: github.com/weiwuji/opc-starter

10. Verification Checklist (Run After Deployment)

Spend 5 minutes running through this to make sure the whole chain is wired up:

# === 1. Base service health ===
docker compose ps                          # all containers STATUS=Up
docker compose logs --tail=20 api          # no abnormal logs in API
curl http://localhost:8000/health          # expect {"status":"ok"}

# === 2. Database connectivity ===
docker compose exec db mysqladmin ping -h 127.0.0.1 -u root -p${DB_PASSWORD}
# expected: mysqld is alive

# === 3. Redis connectivity ===
docker compose exec redis redis-cli ping
# expected: PONG

# === 4. Celery worker ===
docker compose exec celery celery -A tasks inspect ping
# expected: {'celery@xxx': {'ok': 'pong'}}

# === 5. Nginx + HTTPS ===
nginx -t && nginx -s reload
curl -o /dev/null -s -w "%{http_code}" https://yourdomain.com
# expected: 200

# === 6. SSL certificate validity ===
echo | openssl s_client -servername yourdomain.com \
  -connect yourdomain.com:443 2>/dev/null \
  | openssl x509 -noout -dates
# check that the notAfter date is still in the future

# === 7. GitHub Actions pipeline ===
# In the GitHub repo → Actions → trigger the workflow manually
# confirm all four steps are green: test → build → deploy → restart

# === 8. File cache (FileCache) ===
python -c "
from minio import Minio
fc = FileCache(oss_access_key='test', oss_secret_key='test')
print('FileCache initialized OK:', fc.local_dir)
"
# expected: FileCache initialized OK: /data/cache
Enter fullscreen mode Exit fullscreen mode

If everything passes, your OPC infrastructure is production-ready.

In the next article I'll walk you through building the AI automation pipeline — from WeChat content generation, to AI Agent auto-replies, to automatic data rollups into a Notion dashboard. Full-chain automation. One person, the output of ten.

As I wrote in my private notes: "While others are still agonizing over whether their server is big enough, Wu Ji has already closed the loop on a thousand-yuan budget. Not because the tech is fancy — because he knows what to save on and what not to."

See you in the next one.

About the author: Wu Ji (无记) — AI & digitalization practitioner focused on Agent engineering, Loop Engineering, and digital transformation. Practical, hands-on tutorials — follow along and it just works.

Top comments (0)