Container Deployment¶
Deploy SkillMeat as containerized services using Docker or Podman. The compose configuration includes profiles for every use case — from zero-config local development to production enterprise deployments.
Prerequisites¶
- Docker or Podman (with compose support)
- Docker Desktop: Includes Docker Compose
- Podman: Install
podman-composevia your package manager - Disk space: 3-5 GB for images (API, Web, Docs, PostgreSQL layers), plus 1 GB per PostgreSQL database (enterprise only). Note: Podman/Docker storage accumulates over time with repeated builds — clean up old images periodically with
podman system prune -afif disk usage becomes a concern. - Memory: 2 GB (local profile), 4 GB+ (enterprise profile)
- Ports: 3000 (web), 8080 (API), 8000 (docs), 5432 (PostgreSQL if enterprise)
Profile Selection Matrix¶
Choose the profile that matches your deployment scenario:
| Profile | Database | Auth | Services | Ports Used |
|---|---|---|---|---|
local |
SQLite (embedded) | None | API, Web, Docs | 3000, 8080, 8000 |
local-auth |
SQLite (embedded) | Clerk API | API, Web, Docs | 3000, 8080, 8000 |
enterprise |
PostgreSQL (you provide) | Configurable | API, Web, Docs, PostgreSQL | 3000, 8080, 8000, 5432 |
full |
PostgreSQL + demo data | Clerk API | API, Web, Docs, PostgreSQL, Backstage, demo-db | 3000, 8080, 8000, 5432, 7007 |
api-only |
PostgreSQL (you provide) | — | API, demo-db | 8080, 5432 |
backstage-only |
PostgreSQL (you provide) | — | Backstage, demo-db | 7007, 5432 |
Start with local unless you need PostgreSQL or team authentication.
Quick Start (No Source Code Required)¶
If you don't have the SkillMeat repository, download the compose files and configuration templates.
1. Download Compose Files¶
Download these files to a deployment directory:
docker-compose.yml— Main service definitionsdocker-compose.dev.yml— Dev overrides (hot-reload)docker-compose.monitoring.yml— Optional observability stackcompose.sh— Docker/Podman wrapper script (optional, for convenience)
2. Choose Environment Template¶
Copy the appropriate .env template to .env in the same directory:
3. Start Services¶
# Using downloaded compose file (Docker)
docker compose --profile local up -d
# Using downloaded compose file (Podman)
podman-compose --profile local up -d
# Or, if you downloaded compose.sh:
chmod +x compose.sh
./compose.sh --profile local up -d
4. Verify Deployment¶
# Check service health
curl http://localhost:8080/health
# View logs
docker compose logs -f skillmeat-api # or: podman-compose logs -f
# Access web UI
open http://localhost:3000 # macOS
xdg-open http://localhost:3000 # Linux
start http://localhost:3000 # Windows
Services are ready when:
- API health check returns: {"status": "ok"}
- Web UI loads: http://localhost:3000
- Documentation available: http://localhost:8000
Quick Start (With Source Code)¶
If you have the SkillMeat repository:
# Clone the repository
git clone https://github.com/miethe/skillmeat.git
cd skillmeat
# Copy env template for your profile
cp .env.local.example .env
# Start with the wrapper script (optional)
chmod +x compose.sh
./compose.sh --profile local up -d
# Or, use docker compose directly
docker compose --profile local up -d
The wrapper script (compose.sh) detects Docker vs Podman automatically and passes all flags through.
Using compose.sh on Podman (Recommended)¶
If you're using Podman (not Docker), you must use the compose.sh wrapper script instead of running podman-compose directly. Here's why:
- Builds images reliably — Pre-builds images before compose (Podman's compose command doesn't reliably forward build arguments)
- Sets BUILDAH_ULIMIT — Prevents "too many open files" (EMFILE) errors during Next.js builds
- Forwards build variables — Automatically passes all
NEXT_PUBLIC_*environment variables as--build-argto the web image (critical: Next.js inlines these at build time, not runtime) - Detects Docker vs Podman — Works seamlessly with both; no manual switching needed
- Manages Podman socket — Handles socket setup automatically
If you're using Docker, docker compose works fine directly. You can skip compose.sh entirely. However, using compose.sh does no harm — it auto-detects Docker and behaves identically to docker compose.
Example (Podman):
Example (Docker):
# Direct docker compose (equally valid)
docker compose --profile local up -d --build
# Or use compose.sh — it auto-detects Docker
./compose.sh --profile local up -d --build
Environment Variables¶
Common Variables (All Profiles)¶
# Server
SKILLMEAT_HOST=0.0.0.0
SKILLMEAT_PORT=8080
SKILLMEAT_ENV=production
# Web UI
SKILLMEAT_WEB_PORT=3000
SKILLMEAT_DOCS_PORT=8000
# GitHub integration (optional, for artifact discovery)
SKILLMEAT_GITHUB_TOKEN=ghp_your_token
Enterprise Profile Additional¶
# Edition
SKILLMEAT_EDITION=enterprise
# PostgreSQL (required for enterprise)
DATABASE_URL=postgresql://skillmeat:password@postgres:5432/skillmeat
POSTGRES_PASSWORD=your_secure_password
# Auth provider (configurable)
SKILLMEAT_AUTH_PROVIDER=clerk # or "local"
CLERK_SECRET_KEY=sk_live_...
CLERK_PUBLISHABLE_KEY=pk_live_...
Local-Auth Profile Additional¶
# Clerk authentication (for team access)
CLERK_SECRET_KEY=sk_live_...
CLERK_PUBLISHABLE_KEY=pk_live_...
All env examples include extensive inline comments explaining each variable. Open the .env.*example file you're using for complete reference.
First-Run Startup Sequence¶
When you start services for the first time:
- API container starts and detects the database type (SQLite for
local, PostgreSQL forenterprise) - Alembic migrations run automatically — the container creates all required tables
- Health checks monitor readiness — API waits until migrations complete
- Web and Docs services start once the API is healthy
- Logs show "Uvicorn running" — service is ready to accept requests
Monitor first-run startup:
# Watch API logs for migrations
docker compose logs -f skillmeat-api
# Expected output:
# Running migrations...
# INFO [alembic.migration] Running upgrade ... → ...
# Application startup complete
If migrations take longer than 30 seconds on first run, the health check timeout may trigger a restart — this is safe and automatic. The container retries until migrations succeed.
Verifying Your Deployment¶
Health Checks¶
# API health
curl http://localhost:8080/health
# Response: {"status": "ok"}
# Web UI (should return HTML, not JSON)
curl -I http://localhost:3000
# Response: HTTP/1.1 200 OK
# Documentation site
curl http://localhost:8000
Checking Logs¶
# All services
docker compose logs
# Specific service
docker compose logs skillmeat-api
docker compose logs skillmeat-web
# Follow logs (tail -f style)
docker compose logs -f
# Last 50 lines
docker compose logs --tail 50
Accessing Services¶
| Service | URL | Purpose |
|---|---|---|
| Web UI | http://localhost:3000 | SkillMeat interface |
| API | http://localhost:8080 | REST endpoints |
| API Docs | http://localhost:8080/docs | Swagger UI |
| Documentation | http://localhost:8000 | MkDocs user guides |
| PostgreSQL | localhost:5432 | Enterprise database (if enabled) |
Common Operations¶
Stop Services (Keep Data)¶
Data persists in Docker volumes. When you run up again, the database is unchanged.
Stop Services and Wipe Data¶
Warning
This deletes all volumes, including the database. Use only when you want a fresh start.
Restart a Service¶
Rebuild After Configuration Changes¶
If you edit .env or update the codebase:
View Service Status¶
Execute Commands Inside a Container¶
# Run a shell in the API container
docker compose exec skillmeat-api bash
# Run a Python script
docker compose exec skillmeat-api python -c "import skillmeat; print(skillmeat.__version__)"
# Access the database (enterprise only)
docker compose exec postgres psql -U skillmeat -d skillmeat -c "SELECT COUNT(*) FROM artifacts;"
Upgrading¶
SkillMeat releases are published as Docker images. To upgrade:
1. Pull New Images¶
2. Restart Services¶
Migrations run automatically in the API container. If any migration fails, the container logs will show the error — review it before restarting.
3. Verify Upgrade¶
Enterprise Deployment (PostgreSQL)¶
Before Starting¶
Ensure PostgreSQL 14+ is accessible and you have connection details:
Environment Setup¶
Edit .env after copying .env.enterprise.example:
# Database
DATABASE_URL=postgresql://skillmeat:password@your-db-host:5432/skillmeat
# Authentication
SKILLMEAT_EDITION=enterprise
SKILLMEAT_AUTH_PROVIDER=clerk
CLERK_SECRET_KEY=sk_live_...
CLERK_PUBLISHABLE_KEY=pk_live_...
Start Services¶
First-Run Seeding (Required for Enterprise)¶
Enterprise deployments require database seeding to function properly. Without seeding, key features will be unavailable:
What breaks without seeding?¶
- No admin access —
enterprise_userstable is empty, so no users can log in - Team features disabled —
enterprise_teamsandenterprise_team_membersare empty - Scope features broken — Enterprise pages show "unauthorized" errors
- Default collection missing —
enterprise_collectionsdefault row missing causes downstream inserts to fail silently - Demo artifacts missing — Minimum expected state: 18+ artifacts, 5+ deployments
Seeding procedure¶
After services are healthy, run the seed script:
docker compose exec skillmeat-api python scripts/seed/run.py \
--profile minimal \
--edition enterprise \
--clerk-user-id <your-clerk-user-id>
Finding your Clerk User ID:
Open the Clerk Dashboard under Users and copy your user ID, or:
1. Open the SkillMeat web UI in your browser (http://localhost:3000)
2. Log in with your Clerk account
3. Open Developer Tools (F12) and run: fetch('/api/v1/auth/me').then(r => r.json()).then(d => console.log(d.id))
4. Copy the ID and use it above
Validating seeding succeeded¶
After seeding completes, verify the database state:
docker compose exec postgres psql -U skillmeat -d skillmeat -c "
SELECT 'enterprise_users' as table_name, count(*) as count FROM enterprise_users
UNION ALL SELECT 'enterprise_teams', count(*) FROM enterprise_teams
UNION ALL SELECT 'enterprise_collections (default)', count(*) FROM enterprise_collections WHERE is_default=true
UNION ALL SELECT 'enterprise_artifacts', count(*) FROM enterprise_artifacts
UNION ALL SELECT 'enterprise_deployments', count(*) FROM enterprise_deployments;"
Expected minimums after successful seed: - enterprise_users: >= 1 (your admin account) - enterprise_teams: >= 1 (default team) - enterprise_collections (default): >= 1 (required for operations) - enterprise_artifacts: >= 18 (demo content) - enterprise_deployments: >= 5 (demo deployments)
If any count is zero, the seed may have failed silently. Check logs:
Re-seeding after a clean start¶
If you wipe the database (docker compose down -v) and want to reseed:
# After cleaning and restarting services
docker compose down -v
docker compose --profile enterprise up -d
# Wait for API to be healthy
sleep 10
# Re-seed with same or different Clerk user
docker compose exec skillmeat-api python scripts/seed/run.py \
--profile minimal \
--edition enterprise \
--clerk-user-id <your-clerk-user-id>
The seed script automatically detects the PostgreSQL connection (default: localhost:5432) from environment variables.
Development with Hot-Reload¶
For local development with code changes visible instantly without rebuilding:
# Start with dev overlay (auto-detects Docker vs Podman)
./compose.sh --dev --profile local up -d --build
# Or, manually specify compose files
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile local up -d
The dev overlay:
- Mounts source code into containers (/app/skillmeat for API, /app/skillmeat/web for frontend)
- Runs API with auto-reload on file changes
- Runs Next.js dev server (faster than production build)
Edit files locally, and changes appear immediately in running services. Rebuilds are only needed when dependencies change.
Enterprise development with hot-reload¶
For enterprise development with hot-reload (useful when developing enterprise features):
# Start enterprise profile with dev overlay
./compose.sh --dev --profile enterprise up -d --build
# Or without compose.sh wrapper:
docker compose -f docker-compose.yml -f docker-compose.dev.yml --profile enterprise up -d --build
In dev mode, code changes don't require --build. Edit the file on your host and restart the container:
# After fixing a bug in the API
docker compose restart skillmeat-api
# Check logs to verify the fix
docker compose logs -f skillmeat-api
Only use --build when you modify dependencies (pyproject.toml, package.json, etc.).
Troubleshooting¶
"Port already in use"¶
Another process is using the port. Change the port or stop the conflicting service:
# Find what's using port 8080
lsof -i :8080 # macOS/Linux
netstat -ano | findstr 8080 # Windows
# Use custom port in .env
SKILLMEAT_API_PORT=9080
docker compose --profile local up -d
API crashes during startup with "Migration error"¶
The database has leftover migrations from a previous code version. Fix:
# Option 1: Wipe database and restart (safest for local)
docker compose down -v
docker compose --profile local up -d
# Option 2: Manually stamp past migration (advanced)
docker compose exec skillmeat-api alembic -c skillmeat/cache/migrations/alembic.ini stamp <revision>
Podman: "name is already in use" or "pod already exists"¶
Podman's pod system sometimes leaves stale pods after an interrupted compose session. Clean and restart:
# Force-remove the pod (removes all containers in it)
podman pod rm -f pod_skillmeat
# Restart services
./compose.sh --profile enterprise up -d
This is a Podman-specific issue when a previous up was interrupted or backgrounded.
Podman DNS resolution fails¶
Podman's internal DNS sometimes gets stale entries. Reset the pod and network:
docker compose down
podman pod rm -f pod_skillmeat
podman network rm skillmeat_default
docker compose --profile local up -d
Podman storage and disk space cleanup¶
Monitor and clean up Podman storage to prevent disk exhaustion:
# Check how much Podman storage is using
podman system df
# Remove all unused images and layers (preserves database volumes)
podman system prune -af
# CAUTION: Also removes database volumes (full reset)
podman system prune -af --volumes
Tip
Use podman system prune -af (without --volumes) to clean up old images while preserving your PostgreSQL data. This is safe for regular maintenance.
Warning
podman system prune -af --volumes deletes all volumes, including your database data. Only use this when you want a complete clean slate and don't mind re-seeding the database.
compose.sh exits with code 125 despite services running¶
The wrapper script builds all images (including optional ones like Backstage) even for profiles that don't use them. If a non-essential image build fails (e.g., transient Docker Hub DNS failure), compose.sh reports failure but the essential services may be running fine.
Check status:
# See which services are actually running
podman ps
# Check logs of your profile's core services
podman logs skillmeat-api
podman logs skillmeat-web
podman logs skillmeat-postgres # enterprise only
If the services you need are healthy and running (check /health endpoint), the exit code can be safely ignored.
To avoid this in the future, pre-pull the failing image manually:
podman pull docker.io/library/caddy:2-alpine # or whichever failed
./compose.sh --profile enterprise up -d --build
Transient Docker Hub DNS failures during build¶
Symptom: Build fails with dial tcp: lookup auth.docker.io: no such host or similar DNS error
This is a transient network issue, not a code problem. Pre-pull the failing image and retry:
podman pull docker.io/library/node:18-alpine # or the image that failed
./compose.sh --profile enterprise up -d --build
Enterprise surface validation¶
After deploying enterprise, verify the API surface loaded completely:
# Check total API paths (healthy enterprise: 390+)
curl -s http://localhost:8080/api/v1/openapi.json | \
python3 -c "import sys,json; print(f'API paths: {len(json.load(sys.stdin).get(\"paths\",{}))}')"
A healthy enterprise deployment serves ~390+ OpenAPI paths. Single-digit or zero means the API started in a degraded state (usually from failed migrations or seed issues).
Web image build fails with "ECONNREFUSED" errors¶
When building the web Docker image, you may see connection errors like:
Creating an optimized production build ...
connect ECONNREFUSED 9.46.42.12:8080
Retrying 1/3...
connect ECONNREFUSED 9.46.42.12:8080
This happens because Next.js evaluates the rewrite rules at build time, attempting to connect to INTERNAL_API_URL to validate the configuration. During the build phase, the API server is not yet running, so the connection is refused.
Is this a problem?
Usually, no. Next.js retries automatically and the build succeeds. The rewrite configuration is baked into the final build and works correctly at runtime when the API is available. You can safely ignore the error warnings if the build eventually completes.
If the build actually fails (doesn't complete after retries):
Check that INTERNAL_API_URL isn't misconfigured:
# Check your .env file
grep INTERNAL_API_URL .env
# Default (recommended): This is safe — hostname can't resolve during build
INTERNAL_API_URL=http://skillmeat-api:8080
# Problem: This is a real IP that actively refuses connections
INTERNAL_API_URL=http://192.168.1.100:8080 # ← Will fail
Fix:
-
If using
compose.sh(recommended), the build args are forwarded correctly from.env— just ensureINTERNAL_API_URLdefaults tohttp://skillmeat-api:8080. -
If building directly with
docker buildorpodman build, pass the build arg explicitly: -
If you need a custom API URL for multi-host deployments, use a valid hostname (not an IP) that Docker can't resolve during build — DNS lookup failures are silent, not fatal.
PostgreSQL fails to start (Enterprise)¶
Check PostgreSQL logs and ensure the container has database access:
docker compose logs postgres
# Or, test connection directly from API container
docker compose exec skillmeat-api psql $DATABASE_URL -c "SELECT 1"
Observability (Optional)¶
Deploy monitoring stack (Prometheus + Grafana + Loki) alongside SkillMeat:
Access Grafana dashboards:
See docker-compose.monitoring.yml for customization options.
Docker Compose Reference¶
Common commands:
# Start services (foreground, useful for debugging)
docker compose --profile local up
# Start in background
docker compose --profile local up -d
# Stop services
docker compose down
# Stop and remove all data
docker compose down -v
# View live logs
docker compose logs -f
# Show service status
docker compose ps
# Rebuild images
docker compose build
# Execute command in running container
docker compose exec skillmeat-api bash
Next Steps¶
- Authentication: Authentication Setup Guide
- Server Configuration: Server Setup Guide
- Scaling: Enterprise Governance Guide
- Monitoring: Deploy the
monitoringprofile and review metrics