A lightweight blog app on aiohttp + PostgreSQL, organized under the app/ package.
- App container:
python-awesome-web-app - DB container:
postgres:16-alpine - App URL:
http://127.0.0.1:9000/ - PostgreSQL host port:
55432-> container5432
app/main.py: application bootstrap, middlewares, route registration.app/handlers/: route handlers split by domain (public.py,manage.py,api.py).app/db/orm.py: SQLAlchemy async compatibility ORM.app/db/models.py: model definitions.app/services/: shared business helpers (including sitemap cache/renderer).app/templates/,app/static/,app/config/: templates, frontend assets, and runtime config.scripts/generate_sitemap.py: optional offline sitemap export script.conf/database.postgresql.sql: schema bootstrap SQL.ops/: backup/restore/health-check scripts.
Start services:
docker compose up -d postgres appCheck status:
docker compose ps
docker compose logs --tail=100 app
docker compose logs --tail=100 postgresLocal run (venv):
python3 -m app.mainSitemap endpoint (auto-generated with cache):
curl -s http://127.0.0.1:9000/sitemap.xmlOptional offline sitemap export:
python3 scripts/generate_sitemap.pySlow network build:
PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple docker compose build --no-cache appUse provided scripts:
./ops/backup.sh
./ops/restore.sh /var/backups/python-awesome-web/awesome_blog_<TS>.dumpManual equivalents:
docker compose exec -T postgres pg_dump -U awesome -d awesome_blog -Fc > backups/awesome_blog_$(date +%F_%H%M%S).dump
cat backups/awesome_blog_<TS>.dump | docker compose exec -T postgres pg_restore -U awesome -d awesome_blog --clean --if-exists --no-owner --no-privileges0 4 * * 0 cd /var/www/python-awesome-web && ./ops/backup.sh >> /var/log/python-awesome-web-backup.log 2>&1
30 4 * * 0 find /var/backups/python-awesome-web -name '*.dump' -mtime +90 -delete- On old server: run
./ops/backup.sh. - Copy code + dump to new server.
- Start DB:
docker compose up -d postgres. - Restore dump with
./ops/restore.sh <dump_file>. - Start app:
docker compose up -d --build app. - Verify:
./ops/healthcheck.sh http://127.0.0.1:9000/.
Use a generic server block first (no domain hardcoded), then replace server_name _; later if needed.
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Optional: serve static files directly from Nginx
location /static/ {
alias /var/www/python-awesome-web/app/static/;
access_log off;
expires 7d;
}
# Keep sitemap dynamic (do not treat as static file)
location = /sitemap.xml {
proxy_pass http://127.0.0.1:9000;
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;
}
location / {
proxy_pass http://127.0.0.1:9000;
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;
}
}Apply and verify:
nginx -t && sudo systemctl reload nginx
curl -I http://127.0.0.1:9000/sitemap.xml
curl -I http://127.0.0.1/sitemap.xmlLegacy MySQL migration tooling was removed after cutover. Keep old MySQL dumps offline for archival recovery only.