
Deploy New Service
- 1 installs
- Updated June 23, 2026
- ae5000/app-template-python
deploy-new-service is a Claude Code skill for scaffolding and deploying a new Python FastAPI service to the bgrx platform.
About
deploy-new-service walks through creating a new Python FastAPI service and deploying it to the bgrx platform. A developer uses it when adding a new API or microservice that self-registers with the platform registry via platform_auth.py. It covers scaffolding from app-template-python, editing platform.yaml, wiring platform auth, exposing routes to CLI and MCP, configuring GitHub CI, secrets and Postgres, and the first Docker Swarm deploy.
- Scaffolds a new Python FastAPI service from app-template-python
- Self-registers with a platform registry and deploys to Docker Swarm via CI
- Covers platform.yaml config, secrets, Postgres provisioning, and CLI/MCP exposure
Deploy New Service by the numbers
- 1 all-time installs (skills.sh)
- Ranked #1,172 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Jul 7, 2026 (Skillselion catalog sync)
deploy-new-service capabilities & compatibility
- Capabilities
- service scaffolding · deployment · ci cd · secrets management
- Works with
- github · docker · postgres
- Use cases
- devops · ci cd · api development
What deploy-new-service says it does
New services are Python FastAPI apps that self-register with the platform registry via `platform_auth.py`.
CI deploys on every push to `main` via `docker service update --image ... --update-order start-first`.
npx skills add https://github.com/ae5000/app-template-python --skill deploy-new-serviceAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1 |
|---|---|
| Last updated | June 23, 2026 |
| Repository | ae5000/app-template-python ↗ |
How do you add a new FastAPI service to the platform and deploy it to production correctly?
Scaffold and deploy a new FastAPI service to the bgrx platform with auth, CI, secrets, and Swarm deployment.
Who is it for?
Developers adding a new microservice or API to the bgrx platform and deploying it to production.
Skip if: Non-FastAPI stacks or platforms without the bgrx registry and platform_auth conventions.
When should I use this skill?
Creating a new service, adding a new API, deploying a new service, or scaffolding a microservice.
What you get
A registered FastAPI service with a subdomain, CI deploy, secrets, and platform auth wired in.
- A registered FastAPI service with a subdomain
- A CI deploy workflow and platform.yaml config
By the numbers
- 5 main setup steps (repo, platform.yaml, service, CI, deploy)
- Uses a Python 3.12-slim Docker image
Files
Deploying a New Service to bgrx Platform
Overview
New services are Python FastAPI apps that self-register with the platform registry via platform_auth.py. Once registered, they appear in the portal, get a subdomain, and are accessible via the CLI and MCP.
---
Step 1 — Create the service repo
Use app-template-python as the starting point:
# Copy the template or use it as a GitHub template repo
cp -r /path/to/app-template-python /path/to/your-service
cd your-serviceTemplate contains:
main.py— FastAPI app with platform auth wired inplatform_auth.py— copy this into every service (no external SDK)platform.yaml— service metadata and access controlrequirements.txt— app dependencies (no platform-sdk needed)Dockerfile— standard Python 3.12-slim imagedeploy-service.sh— first-deploy helper (reads from platform-infra)
---
Step 2 — Edit platform.yaml
service:
name: your-service-name # must be unique, lowercase, hyphens ok
group: engineering # team that owns it
description: "What this service does"
owners:
- your-email@example.com
runtime:
port: 8000
health_check: /health # platform-registry polls this
expose:
preset: all # expose all routes to platform
access:
require_any_group: # who can see/call this service
- engineering
# Optional: provision a dedicated Postgres database (see Step 4b)
# database:
# postgres: true
# Optional: inject secrets from .env.secrets (see Step 4b)
# secrets:
# - MY_API_KEY
# - STRIPE_SECRET_KEYname becomes the subdomain: https://your-service-name.{domain}.
---
Step 3 — Write the service (main.py)
Minimal pattern:
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends
from platform_auth import PlatformAuthMiddleware, platform_lifespan, current_user, PlatformUser
@asynccontextmanager
async def lifespan(app):
async with platform_lifespan(app): # registers with registry, starts heartbeat, auto-adds /health
yield
app = FastAPI(title="your-service-name", lifespan=lifespan)
app.add_middleware(PlatformAuthMiddleware)
@app.get("/api/items", summary="List items")
async def list_items(user: PlatformUser = Depends(current_user)):
return []Key rules:
platform_lifespan(app)— pass the app object so it auto-registers/healthand sends OpenAPI schema to the registry on startupPlatformAuthMiddleware— validatesX-Platform-Authheader on every requestDepends(current_user)— injects authenticated user into protected routes- Do not define `/health` yourself —
platform_lifespan(app)registers it automatically - Dev bypass:
DEV_MOCK_USER=email:group1,group2skips auth with mock user
For group-restricted operations:
@app.delete("/api/items/{id}")
async def delete_item(id: str, user: PlatformUser = Depends(current_user)):
user.require_group("engineering") # 403 if user not in group
...Step 3b — Expose routes to CLI + MCP
Routes are invisible to bgrx and Claude Desktop MCP by default. Add openapi_extra:
@app.get(
"/api/items",
summary="List all items",
openapi_extra={"x-platform": {
"cli": {"command": "your-service list-items"}
# "mcp" auto-derived: tool_name = "your-service_list-items"
}},
)Without this, bgrx services shows the service but bgrx your-service --help has no subcommands, and no MCP tools are registered. See app-template-dev skill for full args schema.
---
Step 4 — Set up GitHub repo and CI
1. Create GitHub repo (in your org, not personal) under the bgrx org 2. Add the deploy workflow — copy .github/workflows/deploy.yml from hello-service or auth-service 3. Set required GitHub repo secrets/variables:
Secrets (set at org level or per-repo):
REGISTRY_NAME— DOCR registry name (e.g.registry.digitalocean.com/bgrx)SSH_PRIVATE_KEY— SSH key for manager node
Variables (repo-level):
SWARM_SERVICE_NAME— set ONLY if the repo name ≠ service name inplatform.yaml. If repo ismy-serviceand service name ismy-service, skip this. If repo isbgrx-my-servicebut service name ismy-service, set this tomy-service.
CI deploys on every push to main via docker service update --image ... --update-order start-first.
---
Step 4b — Configure database and secrets (optional)
If your service needs a Postgres database or external API secrets:
1. In `platform.yaml`, uncomment the relevant sections:
database:
postgres: true # provisions a dedicated DB + user, stored as DATABASE_URL secret
secrets:
- MY_API_KEY # must have a matching entry in platform-infra/.env.secrets
- STRIPE_SECRET_KEY2. In `platform-infra/.env.secrets` (gitignored, create from .env.secrets.example):
YOUR_SERVICE_MY_API_KEY=sk-...
YOUR_SERVICE_STRIPE_SECRET_KEY=sk_live_...Key naming: {SERVICE_NAME_WITH_UNDERSCORES_UPPERCASE}_{VAR_NAME} where hyphens in service name become underscores.
3. Read secrets in your service via platform_auth.read_secret():
from platform_auth import read_secret
DATABASE_URL = read_secret("DATABASE_URL") # Postgres if database.postgres: true
MY_API_KEY = read_secret("MY_API_KEY") # from secrets: listread_secret() reads /run/secrets/<name> in production, falls back to env var in local dev.
---
Step 5 — First deploy
If the service has no database or secrets (platform.yaml has none of the optional sections):
cd /path/to/platform-infra
bash scripts/deploy-service.sh your-service-nameIf the service uses `database.postgres: true` or `secrets:`, use provision-service.sh instead — it creates the Postgres DB, Swarm secrets, and the Docker service in one pass:
cd /path/to/platform-infra
# Ensure .env has POSTGRES_ADMIN_URL set (DO managed Postgres admin URL)
# Ensure .env.secrets has values for all secrets in platform.yaml
bash scripts/provision-service.sh your-service-name --service-dir=/path/to/your-serviceprovision-service.sh is idempotent — safe to re-run if interrupted. It skips any resource that already exists (checks via docker secret inspect).
After first deploy, all future image updates happen automatically via GitHub CI on push to main.
---
What provision-service.sh does
1. Parses platform.yaml from --service-dir 2. If database.postgres: true:
- Generates a random password
- Creates Postgres user
svc_{service_prefix}and database{service_prefix}(idempotent) - Stores
postgresql://svc_...@host/db?sslmode=requireas Swarm secret{prefix}_db_url - Mounts it at
/run/secrets/DATABASE_URLin the container
3. For each name in secrets::
- Reads
{SERVICE_PREFIX}_{VAR_NAME}from.env.secrets - Creates Swarm secret
{prefix}_{var_lower} - Mounts it at
/run/secrets/{VAR_NAME}in the container
4. Creates the Docker service with --secret flags for all secrets
---
Step 6 — Verify
# Check service registered and healthy in portal
open https://portal.{domain}
# Health endpoint
curl https://your-service-name.{domain}/health
# Check swarm (from platform-infra):
ssh root@{MANAGER_IP} 'docker service ls | grep your-service-name'The service appears in the portal within ~30 seconds of the first successful heartbeat. If it shows as "down", the service started but platform_lifespan(app) isn't running or can't reach the registry.
---
Dockerfile
Standard — don't change unless you have a specific reason:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Services run on port 8000 internally. Traefik terminates TLS externally. Never expose ports directly in the swarm service definition.
---
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Service shows "down" in portal | Not heartbeating | platform_lifespan(app) not called, or registry unreachable |
| "No server available" in Traefik | Service registered but health check fails | Missing /health — pass app to platform_lifespan(app) so it auto-registers it |
| 403 on all routes | Auth middleware not wired | Add app.add_middleware(PlatformAuthMiddleware) |
| Service not visible in portal | User not in require_any_group groups | Add user to platform-infra/config/users.yaml, redeploy auth-service with new Docker config version |
bgrx your-service --help has no subcommands | Routes missing x-platform annotation | Add openapi_extra={"x-platform": {"cli": {"command": "..."}}} to each route |
| CLI/MCP see no routes after adding annotations | Service deployed with old OpenAPI | Redeploy the service — registry only gets updated OpenAPI on startup registration |
| CI deploys to wrong service | Repo name ≠ service name | Set SWARM_SERVICE_NAME repo variable |
| 0 replicas after deploy | Image pull failed | Check DOCR auth, confirm image exists in registry |
Docker config update fails: AlreadyExists | Docker configs are immutable | Create new versioned config (e.g. users-config-v2), update service with --config-rm old --config-add new |
read_secret("X") returns empty string | Secret not mounted | Check Swarm secret exists: ssh manager 'docker secret ls'; re-run provision-service.sh if missing |
read_secret("DATABASE_URL") returns env var in prod | /run/secrets/DATABASE_URL not mounted | Confirm service was created with provision-service.sh, not deploy-service.sh; check docker service inspect for Secrets |
psql: command not found during provisioning | psql client not installed | brew install libpq && brew link --force libpq |
ERROR: Secret value missing from .env.secrets | Key not in secrets file | Add {SERVICE_PREFIX}_{VAR}=value to platform-infra/.env.secrets |
bgrx CLI returns HTML instead of JSON | CF Access blocking programmatic requests | CLI calls go through cli.bgrx.win/proxy/ — check cli-service is running |
WebSocket connects as ws:// not wss:// | Traefik always receives HTTP internally | Fixed in template — wsBase uses location.protocol client-side, not server-side header |
Related skills
FAQ
How does a new service register?
It self-registers with the platform registry via platform_lifespan(app), which also auto-adds a /health endpoint and sends the OpenAPI schema.
How is it deployed?
CI deploys on every push to main via docker service update with a start-first update order.