中文 | English
A modern blog built with Go + React + PostgreSQL. Docker-first deployment.
- Backend: Go 1.25, Gin, PostgreSQL 18, GORM, JWT
- Frontend: React 19, Vite, TailwindCSS, TypeScript
Prerequisites: Docker ≥20.10, Docker Compose ≥2.0, 2GB+ RAM.
# 1. Clone
git clone https://github.com/voocel/blog.git blog
cd blog
# 2. Init
./scripts/init.sh
# 3. Env (required)
cp .env.example .env
vim .env # set POSTGRES_PASSWORD
# 4. Start
docker compose up -d
# 5. Create admin
docker compose run --rm backend ./blog -create-admin
# 6. Visit
# http://localhost or http://your-server-ipUpdate deploy:
git pull
docker compose up -d --buildRoot .env (for deploy):
POSTGRES_PASSWORD(required, for PostgreSQL container init)
Backend runtime config:
config/config.yaml(single source for backend settings, including JWT/DB/App/HTTP)
Frontend build-time options:
- Deploy: set
VITE_API_KEY/VITE_DEFAULT_HOMEPAGEinweb/.env, thendocker compose build frontend && docker compose up -d frontend - Dev: set
VITE_API_KEY/VITE_DEFAULT_HOMEPAGEinweb/.env(optionalVITE_API_URL=http://localhost:8080/api/v1), thennpm run dev
Config files:
.env.example(root): deploy env template.env(root): deploy env (not committed)config/example.yaml: backend templateconfig/config.yaml: backend config (generated by init, not committed)web/.env.example: frontend dev templateweb/.env: frontend dev config (not committed)
Backend priority: config.yaml > code defaults (no backend env override).
Keep config/config.yaml postgres.password consistent with root .env POSTGRES_PASSWORD.
Prereqs: domain points to server IP; port 80 open.
./scripts/setup-https.shEnter domains comma-separated (e.g. example.com,www.example.com). Script will:
- Install acme.sh
- Issue Let’s Encrypt cert
- Enable Nginx HTTPS
- Set auto-renew
Access:
- https://your-domain.com
- http://your-domain.com (redirects to HTTPS)
Backup (old server):
./scripts/backup.shIncludes DB, uploads/avatars, .env, SSL certs, GeoIP.
Restore (new server):
curl -fsSL https://get.docker.com | sh
git clone https://github.com/voocel/blog.git blog
cd blog
./scripts/init.sh
scp backup_*.tar.gz user@new-server:~/blog/
./scripts/restore.sh # choose backup file# status
docker compose ps
# logs
docker compose logs -f
docker compose logs -f backend
docker compose logs -f nginx
# restart/stop
docker compose restart
docker compose stop
# update
git pull && docker compose up -d --build
# shell
docker compose exec backend sh
docker compose exec postgres psql -U postgres blog
# manual DB backup/restore
docker exec blog-postgres pg_dump -U postgres blog > backup.sql
docker exec -i blog-postgres psql -U postgres blog < backup.sqlService won’t start:
docker compose logs -f
netstat -tunlp | grep -E '80|443|8080|5432'
docker compose down
docker compose up -d --buildDB connection issues:
docker exec blog-postgres pg_isready -U postgres
docker compose exec backend cat /app/config/config.yaml
docker compose restart postgresPort conflicts: edit docker-compose.yml ports:
nginx:
ports:
- "8090:80"
- "8443:443"Cert issuance fails: ensure DNS points correctly, port 80 open, firewall allows traffic.
GeoIP: download GeoLite2-City.mmdb (e.g. https://github.com/P3TERX/GeoLite.mmdb), place at config/GeoLite2-City.mmdb, restart backend.
Security hygiene:
- Change default admin password
- Strong secrets in
config/config.yamland.env(PostgreSQL password) - Enable HTTPS
- Firewall (ufw allow 80/443/22; ufw enable)
- Regular backups
- Keep system & Docker updated
Backend:
go run cmd/blog/main.goFrontend:
cd web
npm install
cp .env.example .env
# set VITE_API_URL=http://localhost:8080/api/v1 as needed
# optional: VITE_DEFAULT_HOMEPAGE=blog
npm run devApache-2.0