Deployment¶
This guide covers deploying Gatekeeper to a production server.
Architecture¶
A typical Gatekeeper deployment looks like this:
Internet
↓
┌─────────────────────────────────────────┐
│ nginx (routing server) │
│ - Routes to Gatekeeper on auth.* │
│ - Routes to apps on *.example.com │
│ - Uses auth_request for protection │
└─────────────────────────────────────────┘
↓ ↓
┌──────────────┐ ┌──────────────┐
│ Gatekeeper │ │ Your Apps │
│ (port 8001) │ │ (various) │
└──────────────┘ └──────────────┘
Gatekeeper runs as a systemd service. nginx handles SSL termination and routing.
Server setup¶
1. Install dependencies¶
# Ubuntu/Debian
sudo apt update
sudo apt install nginx certbot python3-certbot-nginx python3.12 python3.12-venv
# Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh
2. Clone and install¶
cd /opt
sudo git clone https://github.com/snehasaisneha/gatekeeper
cd gatekeeper
sudo uv sync
3. Configure¶
Create the environment file:
sudo cp .env.example /opt/gatekeeper/.env
sudo nano /opt/gatekeeper/.env
Set production values:
SECRET_KEY=<generate with: openssl rand -hex 32>
DATABASE_URL=sqlite+aiosqlite:////opt/gatekeeper/gatekeeper.db
APP_URL=https://auth.example.com
FRONTEND_URL=https://auth.example.com
COOKIE_DOMAIN=.example.com
PUBLIC_API_DOCS=false
TRUSTED_PROXY_IPS=127.0.0.1,10.0.0.0/8
EMAIL_PROVIDER=ses
# ... other settings
TRUSTED_PROXY_IPS must include only the nginx or routing tiers that are allowed to set X-Forwarded-For / X-Real-IP. Do not put broad public ranges there.
4. Initialize database¶
cd /opt/gatekeeper
sudo uv run all-migrations
sudo uv run gk users add --email admin@example.com --admin --seeded
5. Build frontend¶
cd /opt/gatekeeper/frontend
sudo npm install
sudo npm run build
Systemd service¶
Create /etc/systemd/system/gatekeeper.service:
[Unit]
Description=Gatekeeper Auth Service
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/gatekeeper
Environment="PATH=/opt/gatekeeper/.venv/bin:/usr/local/bin:/usr/bin"
ExecStart=/opt/gatekeeper/.venv/bin/gk ops serve --host 127.0.0.1 --port 8001 --no-reload --workers 4
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Tip
Adjust --workers based on your server’s CPU cores. A good starting point is 2-4 workers per CPU core.
You can also set server options via environment variables instead of CLI flags:
Environment="SERVER_HOST=127.0.0.1"
Environment="SERVER_PORT=8001"
Environment="SERVER_RELOAD=false"
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable gatekeeper
sudo systemctl start gatekeeper
Check status:
sudo systemctl status gatekeeper
sudo journalctl -u gatekeeper -f
nginx configuration¶
Gatekeeper server¶
Create /etc/nginx/sites-available/gatekeeper:
server {
listen 80;
server_name auth.example.com;
# Redirect to HTTPS
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name auth.example.com;
# SSL (add your certificates)
ssl_certificate /etc/letsencrypt/live/auth.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
# Frontend static files
location / {
root /opt/gatekeeper/frontend/dist;
try_files $uri $uri/ /index.html;
}
# API proxy
location /api/ {
proxy_pass http://127.0.0.1:8001;
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;
}
}
Protected app¶
Create a config for each protected app:
# Internal auth check
location = /_gatekeeper/validate {
internal;
proxy_pass http://127.0.0.1:8001/api/v1/auth/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-GK-App "myapp";
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;
proxy_set_header Cookie $http_cookie;
}
server {
listen 443 ssl http2;
server_name myapp.example.com;
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
add_header Pragma "no-cache" always;
add_header Expires "0" always;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
location / {
auth_request /_gatekeeper/validate;
error_page 401 = @signin;
error_page 403 = @denied;
auth_request_set $auth_user $upstream_http_x_auth_user;
proxy_set_header X-Auth-User $auth_user;
proxy_pass http://127.0.0.1:3000;
}
location @signin {
return 302 https://auth.example.com/signin?redirect=$scheme://$host$request_uri;
}
location @denied {
return 302 https://auth.example.com/request-access?app=myapp;
}
location = /logout {
return 302 https://auth.example.com/signout?redirect=https://auth.example.com/signin?redirect=https://$host/;
}
location = /signout {
return 302 https://auth.example.com/signout?redirect=https://auth.example.com/signin?redirect=https://$host/;
}
}
For internal apps, leave that X-Robots-Tag header on the protected app domain too. If you control the app HTML, add:
<meta name="robots" content="noindex, nofollow, noarchive">
For static docs and other cached internal apps, keep the Cache-Control: no-store headers on the
protected app nginx block. That avoids stale pages appearing to remain logged in after signout.
Enable and reload:
sudo ln -s /etc/nginx/sites-available/gatekeeper /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
SSL certificates¶
Use Let’s Encrypt for free certificates:
sudo certbot --nginx -d auth.example.com
sudo certbot --nginx -d myapp.example.com
Certbot automatically renews certificates.
Existing deployments¶
If your auth domain is already live, make sure the auth.example.com nginx server block sends:
add_header X-Robots-Tag "noindex, nofollow, noarchive" always;
Then redeploy the frontend so /robots.txt is published, reload nginx, and verify:
curl -I https://auth.example.com
curl https://auth.example.com/robots.txt
The response should include X-Robots-Tag: noindex, nofollow, noarchive, and robots.txt should disallow all crawlers.
Health checks¶
Test that everything is working:
# Check service
sudo systemctl status gatekeeper
# Check API
curl -s http://127.0.0.1:8001/api/v1/health
# Check from outside
curl -s https://auth.example.com/api/v1/health
Updating¶
To update Gatekeeper:
cd /opt/gatekeeper
sudo git pull
sudo uv sync
sudo uv run all-migrations
cd frontend && sudo npm install && sudo npm run build
sudo systemctl restart gatekeeper
Backups¶
For SQLite, back up the database file:
cp /opt/gatekeeper/gatekeeper.db /backup/gatekeeper-$(date +%Y%m%d).db
For PostgreSQL, use pg_dump:
pg_dump gatekeeper > /backup/gatekeeper-$(date +%Y%m%d).sql