Deploy 25+ Go/Node.js microservices on Ubuntu 22.04 LTS with PostgreSQL, Redis, NGINX, and automated SSL.
ignition/
βββ config.yaml # YOUR SECRETS (gitignored, never commit)
βββ config.example.yaml # Template (commit this)
βββ inventory.ini # Server inventory
βββ ansible.cfg # Ansible configuration
βββ .gitignore # Protects secrets
βββ README.md # This file
βββ playbooks/
β βββ provision.yml # Server provisioning
β βββ deploy.yml # Service deployment
β βββ nginx.yml # NGINX + SSL setup
β βββ backup.yml # Automated backups
β βββ restore.yml # Restore from backup
βββ templates/
βββ systemd-service.j2 # Systemd unit template
βββ nginx-site.conf.j2 # NGINX config template
# Ubuntu/Debian
sudo apt update && sudo apt install ansible
# macOS
brew install ansible
# Verify
ansible --version# Copy template
cp config.example.yaml config.yaml
# Edit with your values
nano config.yamlIn config.yaml, each service needs:
services:
- name: auth-service # Service identifier
type: go # 'go' or 'node'
github_repo: auth-service # GitHub repo name
backend_port: 8001 # Unique port
frontend_enabled: true # Has frontend?
frontend_port: 3001 # Frontend port (if enabled)
database_enabled: true # Needs database?
database_name: auth_db # Database name
run_migrations: true # Run migrations?Important: Each service reads its own config.yaml from its repository. This Ansible config only handles:
- β Which repos to clone
- β Port assignments
- β Database creation
- β Service orchestration
# Generate SSH key
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/github_deploy_key
# Add public key to GitHub org
cat ~/.ssh/github_deploy_key.pub
# Go to: https://github.com/organizations/YOUR-ORG/settings/keys
# Add as deploy key with READ access only
# Add private key to config.yaml
cat ~/.ssh/github_deploy_key
# Paste entire output (including BEGIN/END lines) into config.yamlCreate A records for each service:
api.auth-service.example.com β YOUR_SERVER_IP
app.auth-service.example.com β YOUR_SERVER_IP
api.user-service.example.com β YOUR_SERVER_IP
app.user-service.example.com β YOUR_SERVER_IP
# ... for all services
# Step 1: Provision server (one time)
ansible-playbook -i inventory.ini playbooks/provision.yml -e @config.yaml
# Step 2: Deploy all services
ansible-playbook -i inventory.ini playbooks/deploy.yml -e @config.yaml
# Step 3: Setup NGINX + SSL
ansible-playbook -i inventory.ini playbooks/nginx.yml -e @config.yamlserver:
hostname: 123.45.67.89 # Server IP or domain
ssh_user: root # SSH user
ssh_key_path: ~/.ssh/id_rsa # SSH private key pathgithub:
organization: your-github-org # GitHub organization name
ssh_deploy_key: | # Private deploy key
-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----database:
postgresql:
version: 17
admin_user: postgres
admin_password: "YourStrongPassword123!" # Change this
port: 5432
redis:
password: "AnotherStrongPassword456!" # Change this
port: 6379Each service in the services array:
- name: payment-service # Unique service name
type: node # 'go' or 'node'
github_repo: payment-service # Repo name (not full URL)
backend_port: 8003 # Unique backend port (8001-8999)
frontend_enabled: false # true if has frontend
frontend_port: 3003 # Frontend port (3001-3999) if enabled
database_enabled: true # true if needs PostgreSQL
database_name: payment_db # Database name
run_migrations: true # true to run migrations on deployService Config Files: Each service repo should have its own config.yaml with:
- Database connection strings
- API keys
- Third-party credentials
- Service-specific settings
# Re-deploy one service after code changes
ansible-playbook -i inventory.ini playbooks/deploy.yml -e @config.yaml \
--extra-vars "services=[{'name':'auth-service','type':'go','github_repo':'auth-service','backend_port':8001,'database_enabled':true,'database_name':'auth_db'}]"# SSH into server
ssh deployer@YOUR_SERVER_IP
# Check service status
sudo systemctl status auth-service
sudo systemctl status auth-service-frontend
# View logs
sudo journalctl -u auth-service -f
sudo journalctl -u auth-service-frontend -f
# Restart service
sudo systemctl restart auth-serviceansible-playbook -i inventory.ini playbooks/backup.yml -e @config.yaml# List backups
ssh deployer@YOUR_SERVER_IP "ls -lh /var/backups/automated/"
# Restore (will prompt for timestamp)
ansible-playbook -i inventory.ini playbooks/restore.yml -e @config.yaml# Connect to PostgreSQL
ssh deployer@YOUR_SERVER_IP
sudo -u postgres psql
# List databases
\l
# Connect to specific database
\c auth_db
# List tables
\dt
# Connect to Redis
redis-cli
AUTH your_redis_password
PING
KEYS *After deployment, services are accessible at:
Backend APIs:
https://api.auth-service.example.com
https://api.user-service.example.com
https://api.payment-service.example.com
Frontend Apps:
https://app.auth-service.example.com
https://app.user-service.example.com
https://app.catalog-service.example.com
- β UFW firewall (ports 22, 80, 443 only)
- β SSL/TLS certificates via Let's Encrypt
- β Auto-renewal of SSL certificates
- β GitHub deploy key (read-only)
- β PostgreSQL password authentication
- β Redis password protection
- β Encrypted backups (AES-256)
- β Service isolation (dedicated user)
- β Security headers in NGINX
# Check status
sudo systemctl status service-name
# Check logs
sudo journalctl -u service-name -n 50
# Check if port is in use
sudo lsof -i :8001
# Verify binary exists (Go services)
ls -la /home/deployer/apps/service-name/service-name
# Check service config file exists
ls -la /home/deployer/apps/service-name/config.yaml# Check PostgreSQL is running
sudo systemctl status postgresql
# Verify database exists
sudo -u postgres psql -l | grep service_db
# Test connection
sudo -u postgres psql -d service_db -c "SELECT 1;"# Check certificates
sudo certbot certificates
# Renew manually
sudo certbot renew --dry-run
# Check NGINX config
sudo nginx -t# Check DNS
nslookup api.auth-service.example.com
dig api.auth-service.example.com
# Wait for DNS propagation (can take 5-30 minutes)Each service repository should have its own config.yaml:
# Example: auth-service/config.yaml
server:
port: 8001
host: 0.0.0.0
database:
host: localhost
port: 5432
name: auth_db
user: postgres
password: ${DB_PASSWORD} # From environment or config
redis:
host: localhost
port: 6379
password: ${REDIS_PASSWORD}
jwt:
secret: ${JWT_SECRET}
expiry: 24h
logging:
level: info
format: jsonThis separation allows:
- β Service-specific configuration in each repo
- β Infrastructure-level config in Ansible
- β Easy local development
- β Clear separation of concerns
- Add to
config.yaml:
services:
- name: new-service
type: go
github_repo: new-service
backend_port: 8026 # Use next available port
frontend_enabled: false
database_enabled: true
database_name: new_service_db
run_migrations: true- Create DNS records:
api.new-service.example.com β YOUR_SERVER_IP
- Deploy:
ansible-playbook -i inventory.ini playbooks/deploy.yml -e @config.yaml
ansible-playbook -i inventory.ini playbooks/nginx.yml -e @config.yamlService logs are available via journalctl:
# Real-time logs
sudo journalctl -u auth-service -f
# Last 100 lines
sudo journalctl -u auth-service -n 100
# Logs since specific time
sudo journalctl -u auth-service --since "1 hour ago"
# All service logs
sudo journalctl -f | grep -E '(auth|user|payment)-service'NGINX logs:
# Access logs
sudo tail -f /var/log/nginx/api.auth-service.example.com_access.log
# Error logs
sudo tail -f /var/log/nginx/api.auth-service.example.com_error.log- config.yaml is gitignored
- Strong database passwords (20+ chars)
- GitHub deploy key is read-only
- SSH key has passphrase
- UFW firewall enabled
- SSL certificates active
- Backups are encrypted
- Services run as non-root user
- Each service has its own config.yaml with secrets
For issues:
- Check service logs:
sudo journalctl -u service-name -f - Verify configuration:
cat /home/deployer/apps/service-name/config.yaml - Test connectivity:
curl http://localhost:8001/health - Check NGINX:
sudo nginx -t
- Never commit
config.yamlto Git - Keep backup encryption keys safe
- Rotate database passwords regularly
- Monitor service logs for issues