Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Decap CMS OAuth Authentication - Single Site Setup

Step-by-Step Setup

Part 1: Create GitHub OAuth Application

Step 1.1: Navigate to GitHub Settings

  1. Log into GitHub
  2. Click your profile picture (top right)
  3. Go to Settings
  4. Scroll down in left sidebar
  5. Click Developer settings (near bottom)
  6. Click OAuth Apps
  7. Click New OAuth App button

Step 1.2: Fill Out OAuth App Form

You’ll see a form with several fields:

Application name:

My Site CMS

(Replace “My Site” with your actual site name, e.g., “Boeygembos CMS”)

Homepage URL:

https://www.mysite.com

(Your actual website URL - must include https://)

Application description: (Optional)

OAuth authentication for Decap CMS content management

Authorization callback URL:

https://oauth.mysite.com/callback

Important notes about the callback URL:

  • This is where GitHub redirects after authentication
  • Must be https:// (not http://)
  • /callback path is important - don’t change it
  • Can use subdomain of your site OR your VPS domain
  • Examples:
    • https://oauth.boeygembos.be/callback ✅
    • https://oauth.mysite.com/callback ✅
    • https://mysite.com/oauth/callback ✅

Which domain to use for OAuth?

  • Same domain as site: oauth.mysite.com (cleaner, recommended)
  • VPS domain: oauth.yourvps.com (works for multiple sites)
  • Either works - just be consistent!

Step 1.3: Register and Save Credentials

  1. Click Register application
  2. You’ll see the app overview page

Copy the Client ID:

  • Displayed on screen (looks like: Iv1.abcdef123456)
  • Save this somewhere secure

Generate Client Secret:

  1. Click Generate a new client secret button
  2. IMPORTANT: Copy the secret immediately!
  3. It’s only shown once - you can’t see it again
  4. Save it securely (looks like: abc123def456...)

Keep these safe:

Client ID: Iv1.abcdef123456
Client Secret: abc123def456ghi789jkl012mno345pqr678

You’ll need both in the next steps.


Part 2: Prepare DNS

Step 2.1: Choose OAuth Domain

Decide where your OAuth provider will live:

Option A: Subdomain of your site (Recommended)

oauth.mysite.com
  • Clean and organized
  • Each site gets its own OAuth subdomain

Option B: Shared OAuth domain

oauth.yourvps.com
  • Can be reused for multiple sites
  • Slightly more complex setup later

For this guide, we’ll use: oauth.mysite.com

Step 2.2: Add DNS Record

Log into your DNS provider (Domain.com, Cloudflare, etc.)

Add A Record:

Type: A
Name: oauth (or "oauth.mysite.com" depending on interface)
Value: YOUR_VPS_IP_ADDRESS
TTL: 3600 (1 hour)

Example for Domain.com:

  1. Login → Domain Management
  2. Click your domain → DNS Records
  3. Add Advanced DNS Record
  4. Type: A
  5. Refers to: Select “Other Host” → type oauth
  6. Points to: Your VPS IP (e.g., 123.45.67.89)
  7. TTL: 3600
  8. Save

Step 2.3: Verify DNS

Wait 2-5 minutes, then verify:

# From your computer or VPS
dig oauth.mysite.com +short

Should return your VPS IP address.

Or use online tool: https://dnschecker.org/


Part 3: Deploy OAuth Provider on VPS

Step 3.1: Create Directory Structure

SSH into your VPS:

ssh root@YOUR_VPS_IP

Create directory:

mkdir -p /opt/docker/decap-oauth
cd /opt/docker/decap-oauth

Step 3.2: Create Node.js Server

Create the OAuth server file:

nano server.js

Paste this code:

const express = require('express');
const cors = require('cors');
const { AuthorizationCode } = require('simple-oauth2');

const app = express();
const port = process.env.PORT || 3000;

// CORS configuration
app.use(cors({
  origin: true,
  credentials: true
}));

// OAuth2 configuration
const client = new AuthorizationCode({
  client: {
    id: process.env.OAUTH_CLIENT_ID,
    secret: process.env.OAUTH_CLIENT_SECRET,
  },
  auth: {
    tokenHost: 'https://github.com',
    tokenPath: '/login/oauth/access_token',
    authorizePath: '/login/oauth/authorize',
  },
});

// Redirect to GitHub OAuth
app.get('/auth', (req, res) => {
  const authorizationUri = client.authorizeURL({
    redirect_uri: `${process.env.BASE_URL}/callback`,
    scope: 'repo,user',
    state: req.query.state || '',
  });
  
  console.log('Auth request received, redirecting to GitHub');
  res.redirect(authorizationUri);
});

// OAuth callback
app.get('/callback', async (req, res) => {
  const { code, state } = req.query;
  
  try {
    const tokenParams = {
      code,
      redirect_uri: `${process.env.BASE_URL}/callback`,
    };
    
    const accessToken = await client.getToken(tokenParams);
    const token = accessToken.token.access_token;
    
    // Get the origin from environment
    const redirectOrigin = process.env.REDIRECT_URL;
    
    console.log('Authentication successful, sending token to CMS');
    
    // Send token back to CMS via postMessage
    const script = `
      <!DOCTYPE html>
      <html>
      <head>
        <title>Authentication Successful</title>
        <style>
          body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
          }
          .message {
            text-align: center;
            padding: 2rem;
            background: rgba(255,255,255,0.1);
            border-radius: 10px;
            backdrop-filter: blur(10px);
          }
          .spinner {
            border: 3px solid rgba(255,255,255,0.3);
            border-top: 3px solid white;
            border-radius: 50%;
            width: 40px;
            height: 40px;
            animation: spin 1s linear infinite;
            margin: 20px auto;
          }
          @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
          }
        </style>
      </head>
      <body>
        <div class="message">
          <h1>✓ Authentication Successful</h1>
          <div class="spinner"></div>
          <p>Redirecting back to CMS...</p>
        </div>
        <script>
          (function() {
            function receiveMessage(e) {
              console.log("receiveMessage %o", e);
              window.opener.postMessage(
                'authorization:github:success:${JSON.stringify({ token, provider: 'github' })}',
                e.origin
              );
            }
            window.addEventListener("message", receiveMessage, false);
            window.opener.postMessage("authorizing:github", "${redirectOrigin}");
            
            // Auto-close after 2 seconds
            setTimeout(function() {
              window.close();
            }, 2000);
          })();
        </script>
      </body>
      </html>
    `;
    res.send(script);
  } catch (error) {
    console.error('Authentication failed:', error.message);
    res.status(500).send(`
      <!DOCTYPE html>
      <html>
      <head>
        <title>Authentication Failed</title>
        <style>
          body {
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
            color: white;
          }
          .message {
            text-align: center;
            padding: 2rem;
            background: rgba(255,255,255,0.1);
            border-radius: 10px;
            backdrop-filter: blur(10px);
          }
          .error {
            background: rgba(0,0,0,0.2);
            padding: 1rem;
            border-radius: 5px;
            margin-top: 1rem;
            font-family: monospace;
            font-size: 0.9em;
          }
        </style>
      </head>
      <body>
        <div class="message">
          <h1>✗ Authentication Failed</h1>
          <p>There was an error during authentication.</p>
          <div class="error">${error.message}</div>
          <p style="margin-top: 1rem; font-size: 0.9em;">
            You can close this window and try again.
          </p>
        </div>
      </body>
      </html>
    `);
  }
});

// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    message: 'OAuth provider is running',
    uptime: process.uptime()
  });
});

// Root endpoint
app.get('/', (req, res) => {
  res.json({
    message: 'OAuth Provider for Decap CMS',
    endpoints: {
      auth: '/auth',
      callback: '/callback',
      health: '/health'
    },
    configured_for: process.env.REDIRECT_URL
  });
});

app.listen(port, () => {
  console.log('='.repeat(50));
  console.log('OAuth Provider for Decap CMS');
  console.log('='.repeat(50));
  console.log(`Server listening on port ${port}`);
  console.log(`Base URL: ${process.env.BASE_URL}`);
  console.log(`Redirect URL: ${process.env.REDIRECT_URL}`);
  console.log('='.repeat(50));
});

Save with Ctrl+X, then Y, then Enter.

What this does:

  • /auth - Redirects user to GitHub for authentication
  • /callback - Receives code from GitHub, exchanges for token
  • /health - Health check endpoint
  • / - Shows configuration info

Step 3.3: Create package.json

nano package.json

Paste:

{
  "name": "decap-oauth-provider",
  "version": "1.0.0",
  "description": "OAuth provider for Decap CMS",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.18.2",
    "cors": "^2.8.5",
    "simple-oauth2": "^5.0.0"
  },
  "engines": {
    "node": ">=18.0.0"
  }
}

Save with Ctrl+X, Y, Enter.

Step 3.4: Create Dockerfile

nano Dockerfile

Paste:

FROM node:20-alpine

WORKDIR /app

# Copy package files
COPY package.json ./

# Install dependencies
RUN npm install --production

# Copy application
COPY server.js ./

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \
  CMD node -e "require('http').get('http://localhost:3000/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"

# Start application
CMD ["node", "server.js"]

Save with Ctrl+X, Y, Enter.

Step 3.5: Create docker-compose.yml

nano docker-compose.yml

Paste (replace the placeholder values):

version: '3.8'

services:
  decap-oauth:
    build: .
    container_name: decap-oauth
    restart: unless-stopped
    networks:
      - web
    environment:
      # Server configuration
      - NODE_ENV=production
      - PORT=3000
      
      # OAuth configuration - REPLACE THESE VALUES!
      - OAUTH_CLIENT_ID=YOUR_GITHUB_CLIENT_ID_HERE
      - OAUTH_CLIENT_SECRET=YOUR_GITHUB_CLIENT_SECRET_HERE
      - BASE_URL=https://oauth.mysite.com
      - REDIRECT_URL=https://www.mysite.com
      
    labels:
      # Enable Traefik
      - "traefik.enable=true"
      
      # Router configuration
      - "traefik.http.routers.decap-oauth.rule=Host(`oauth.mysite.com`)"
      - "traefik.http.routers.decap-oauth.entrypoints=websecure"
      - "traefik.http.routers.decap-oauth.tls.certresolver=letsencrypt"
      
      # Service configuration
      - "traefik.http.services.decap-oauth.loadbalancer.server.port=3000"

networks:
  web:
    external: true

IMPORTANT - Replace these values:

  1. OAUTH_CLIENT_ID: Your GitHub Client ID from Part 1
  2. OAUTH_CLIENT_SECRET: Your GitHub Client Secret from Part 1
  3. BASE_URL: Your OAuth provider URL (e.g., https://oauth.mysite.com)
  4. REDIRECT_URL: Your website URL (e.g., https://www.mysite.com)
  5. Host in labels: Change oauth.mysite.com to your actual OAuth domain

Example with real values:

- OAUTH_CLIENT_ID=Iv1.abc123def456
- OAUTH_CLIENT_SECRET=abc123def456ghi789jkl012
- BASE_URL=https://oauth.boeygembos.be
- REDIRECT_URL=https://www.boeygembos.be

Save with Ctrl+X, Y, Enter.

Step 3.6: Build and Start Container

# Build the Docker image
docker compose build

# Start the container
docker compose up -d

# Check if it's running
docker ps | grep decap-oauth

Should show container running.

Step 3.7: Check Logs

docker compose logs

Should see something like:

==================================================
OAuth Provider for Decap CMS
==================================================
Server listening on port 3000
Base URL: https://oauth.mysite.com
Redirect URL: https://www.mysite.com
==================================================

If you see errors, check Troubleshooting section.

Step 3.8: Test OAuth Provider

# Health check
curl https://oauth.mysite.com/health

# Should return:
# {"status":"healthy","message":"OAuth provider is running","uptime":123.45}

# Check configuration
curl https://oauth.mysite.com/

# Should return info about endpoints

If you get errors or 404:

  1. Wait 1-2 minutes for SSL certificate generation
  2. Check Traefik dashboard: https://traefik.yourvps.com
  3. Verify DNS is resolving: dig oauth.mysite.com +short

Part 4: Configure Decap CMS

Step 4.1: Locate Decap CMS Config

In your website repository, find the Decap CMS configuration file.

Common locations:

public/admin/config.yml
static/admin/config.yml
admin/config.yml
src/admin/config.yml

Find it:

# In your local project
find . -name "config.yml" -path "*/admin/*"

Step 4.2: Update Backend Configuration

Edit your config.yml file:

Before (local development only):

backend:
  name: github
  repo: username/repository
  branch: main

After (works in production):

backend:
  name: github
  repo: username/repository
  branch: main
  base_url: https://oauth.mysite.com
  auth_endpoint: auth

Important:

  • Replace username/repository with your actual GitHub repo
  • Replace https://oauth.mysite.com with your actual OAuth provider URL
  • auth_endpoint: auth points to the /auth route in your OAuth provider
  • Keep branch: set to whatever branch you use

Full example:

backend:
  name: github
  repo: boeygembos/boeygembos.github.io
  branch: release
  base_url: https://oauth.boeygembos.be
  auth_endpoint: auth

media_folder: "public/images/uploads"
public_folder: "/images/uploads"

collections:
  - name: "pages"
    label: "Pages"
    files:
      - label: "Home"
        name: "home"
        file: "content/home.md"
        fields:
          - { label: "Title", name: "title", widget: "string" }
          - { label: "Body", name: "body", widget: "markdown" }

Step 4.3: Commit and Deploy

# In your local project
git add public/admin/config.yml  # or wherever your config is

git commit -m "Add OAuth authentication for production CMS"

git push origin main  # or your branch name

If you have GitHub Actions CI/CD set up, wait for deployment (~2 minutes).

If deploying manually:

# Build locally
npm run build

# Upload to VPS (adjust paths)
scp -r dist/* root@YOUR_VPS_IP:/opt/docker/websites/yoursite/html/

# Or rebuild on VPS if that's your workflow

Testing

Test 1: Access CMS Admin

  1. Open your browser
  2. Go to: https://www.mysite.com/admin/
  3. You should see the Decap CMS interface
  4. Look for “Login with GitHub” button

If you don’t see the admin page:

  • Check if /admin/ directory exists in your deployed files
  • Verify Decap CMS is properly configured in your project
  • Check browser console (F12) for errors

Test 2: Authenticate with GitHub

  1. Click “Login with GitHub” button
  2. Should redirect to oauth.mysite.com/auth
  3. Then redirects to GitHub
  4. You’ll see GitHub authorization page:
    • “Authorize [Your App Name]”
    • Shows permissions requested (repo, user)
  5. Click “Authorize [username]”
  6. Should redirect back to your CMS
  7. You should now be logged in! ✅

What you should see:

  • CMS loads your content
  • You can browse collections
  • You can edit entries
  • “Publish” or “Save” buttons are available

Test 3: Edit and Save Content

  1. In the CMS, click on a collection (e.g., “Pages”)
  2. Click on an entry or create a new one
  3. Make a change (edit text, add content)
  4. Click “Publish” or “Save”
  5. Should see success message
  6. Check GitHub repository - should see a new commit!

Verify the commit:

  1. Go to your GitHub repo
  2. Check recent commits
  3. Should see a commit from your GitHub account
  4. Commit message will be from Decap CMS

Test 4: Verify Changes on Live Site

  1. Wait a few minutes (if using CI/CD)
  2. Visit your live website
  3. Changes should be visible
  4. Content you edited should be updated

If changes don’t appear:

  • Check if CI/CD workflow ran successfully
  • Verify deployment completed
  • Clear browser cache and refresh
  • Check if you edited the correct branch

Test 5: OAuth Provider Health

# From your computer or VPS

# Health check
curl https://oauth.mysite.com/health

# Should return:
{
  "status": "healthy",
  "message": "OAuth provider is running",
  "uptime": 12345.67
}

# Check container logs
ssh root@YOUR_VPS_IP
cd /opt/docker/decap-oauth
docker compose logs --tail=50

Look for log entries when you authenticated:

Auth request received, redirecting to GitHub
Authentication successful, sending token to CMS

Troubleshooting

Issue 1: Can’t Access /admin/ Page

Symptoms:

  • https://www.mysite.com/admin/ shows 404
  • CMS interface doesn’t load

Solutions:

  1. Check if admin files exist:
# On VPS
ls -la /opt/docker/websites/yoursite/html/admin/

Should see index.html and config.yml.

  1. Verify build includes admin files:
# Locally, after building
ls -la dist/admin/  # or build/admin/
  1. Check nginx configuration (if using SPA routing):

Make sure your nginx config serves static files correctly:

location /admin/ {
    try_files $uri $uri/ /admin/index.html;
}

Issue 2: “Failed to Load Config” Error

Symptoms:

  • Admin page loads but shows error
  • “Error loading config.yml”
  • Blank page with errors in console

Solutions:

  1. Check config.yml syntax:
# Validate YAML
python3 -c "import yaml; yaml.safe_load(open('public/admin/config.yml'))"
  1. Verify config.yml is deployed:
# On VPS
cat /opt/docker/websites/yoursite/html/admin/config.yml
  1. Check browser console (F12 → Console tab):
  • Look for specific error messages
  • YAML parsing errors
  • 404 errors loading config
  1. Common config errors:
  • Incorrect indentation (YAML is indent-sensitive)
  • Missing required fields
  • Incorrect backend configuration

Issue 3: OAuth Provider Returns 404

Symptoms:

  • Clicking “Login with GitHub” redirects to OAuth provider
  • Shows “404 Not Found” or “Cannot GET /auth”

Solutions:

  1. Check DNS resolves:
dig oauth.mysite.com +short
# Should return your VPS IP
  1. Check container is running:
ssh root@YOUR_VPS_IP
docker ps | grep decap-oauth
  1. Check container logs:
cd /opt/docker/decap-oauth
docker compose logs

Look for errors starting up.

  1. Test directly:
curl https://oauth.mysite.com/health

If this works, OAuth provider is running. Issue is with routing.

  1. Check Traefik configuration:
  • Visit: https://traefik.yourvps.com
  • Look for decap-oauth in HTTP Routers
  • Verify route is configured correctly
  1. Verify SSL certificate:
curl -I https://oauth.mysite.com/health

Should return 200 OK with valid SSL.

Issue 4: GitHub Authorization Fails

Symptoms:

  • Redirects to GitHub successfully
  • After authorizing, get error
  • “Bad credentials” or “Invalid client”

Solutions:

  1. Verify Client ID and Secret:

Check docker-compose.yml has correct values:

cd /opt/docker/decap-oauth
cat docker-compose.yml | grep OAUTH_CLIENT
  1. Regenerate Client Secret:

If you think secret is wrong:

  • Go to: https://github.com/settings/developers
  • Find your OAuth App
  • Click “Generate a new client secret”
  • Update docker-compose.yml with new secret
  • Restart container: docker compose restart
  1. Check callback URL matches:

In GitHub OAuth App settings:

Authorization callback URL: https://oauth.mysite.com/callback

Must EXACTLY match what’s in your config.

  1. Check container logs:
docker compose logs

Look for specific error messages from GitHub.

Issue 5: CORS Errors

Symptoms:

  • Browser console shows CORS error
  • “Access-Control-Allow-Origin” error
  • Authentication fails silently

Solutions:

  1. Verify REDIRECT_URL matches website:

In docker-compose.yml:

- REDIRECT_URL=https://www.mysite.com

Must match your actual site URL EXACTLY:

  • Same protocol (https)
  • Same subdomain (www or not)
  • No trailing slash
  1. Check CORS configuration in server.js:
app.use(cors({
  origin: true,  // Allows all origins
  credentials: true
}));

This should already be correct in the code provided.

  1. Browser console details:

Open browser console (F12) when error occurs:

  • Read the full error message
  • Note which origin is being blocked
  • Verify it matches your site

Issue 6: Organization Access Restrictions

Symptoms:

  • Authentication succeeds
  • Can’t save changes
  • Error: “organization has enabled OAuth App access restrictions”

Solution:

Your repository is in a GitHub organization with OAuth restrictions:

  1. Go to: https://github.com/organizations/YOUR_ORG/settings/oauth_application_policy
  2. Or: https://github.com/settings/connections/applications
  3. Find your OAuth App
  4. Grant organization access
  5. If you’re org owner, access grants immediately
  6. If not, request access (admin must approve)

See detailed instructions in the multi-site guide.

Issue 7: Authentication Works But Can’t Save

Symptoms:

  • Successfully logs in
  • Can view content
  • “Publish” or “Save” fails
  • Error: “Failed to persist entry”

Solutions:

  1. Check GitHub permissions:

User must have write access to the repository:

  • Go to: https://github.com/username/repository/settings/access
  • Verify your account has “Write” or “Admin” access
  • If not, add yourself as a collaborator
  1. Check branch name:

In config.yml:

backend:
  branch: main  # Must match actual branch name

If your default branch is master, change it to master.

  1. Check repository exists:

Verify the repo in config.yml is correct:

backend:
  repo: username/repository  # Check spelling!
  1. Check OAuth app scope:

OAuth app must have repo scope. Verify in server.js:

scope: 'repo,user',  // 'repo' is required

Issue 8: Container Won’t Start

Symptoms:

  • docker compose up -d fails
  • Container keeps restarting
  • docker ps shows container exiting

Solutions:

  1. Check logs:
docker compose logs

Look for specific error messages.

  1. Common startup errors:

Syntax error in server.js:

SyntaxError: Unexpected token

→ Check server.js for typos, missing commas, etc.

Missing dependencies:

Error: Cannot find module 'express'

→ Rebuild container: docker compose build --no-cache

Port already in use:

Error: listen EADDRINUSE: address already in use :::3000

→ Another container using port 3000 → Check: docker ps | grep 3000

  1. Test server.js syntax:
node -c server.js

Should return nothing if syntax is valid.

  1. Rebuild from scratch:
docker compose down
docker compose build --no-cache
docker compose up -d
docker compose logs -f

Security Considerations

Protecting Credentials

Client Secret is sensitive!

✅ DO:

  • Store in docker-compose.yml only
  • Set file permissions: chmod 600 docker-compose.yml
  • Never commit to git with real secrets
  • Keep backups encrypted
  • Rotate periodically (every 6-12 months)

❌ DON’T:

  • Hardcode in server.js
  • Commit to public repositories
  • Share via email/chat
  • Log to console
  • Store in plain text files

File Permissions

cd /opt/docker/decap-oauth

# Restrict access to docker-compose.yml
chmod 600 docker-compose.yml

# Verify
ls -l docker-compose.yml
# Should show: -rw------- (read/write for owner only)

Repository Access Control

Who can edit content:

  • Anyone with write access to the GitHub repository
  • Control via: https://github.com/username/repo/settings/access

Best practices:

  • Only add necessary collaborators
  • Use teams for organization repos
  • Remove access for former team members
  • Review access regularly

OAuth App Security

Monitor your OAuth app:

  1. Go to: https://github.com/settings/developers
  2. Click your OAuth App
  3. Check Recent Delivery activity
  4. Look for unusual patterns

If compromised:

  1. Click “Revoke all user tokens”
  2. Generate new Client Secret
  3. Update docker-compose.yml
  4. Restart container
  5. All users need to re-authenticate

Network Security

Already secured by your setup:

  • ✅ OAuth provider only accessible via HTTPS (Traefik)
  • ✅ No direct port exposure (internal port 3000)
  • ✅ SSL certificates automatic (Let’s Encrypt)
  • ✅ Container isolated on Docker network

Additional measures:

  • Firewall only allows ports 80, 443 (already done)
  • SSH key-only authentication (already done)
  • fail2ban monitoring (already done)

Maintenance

Regular Tasks

Check OAuth Provider Health

Weekly:

# From anywhere
curl https://oauth.mysite.com/health

# Should return healthy status

Monthly:

# SSH to VPS
cd /opt/docker/decap-oauth

# Check container status
docker compose ps

# Check logs for errors
docker compose logs --tail=100 | grep -i error

# Check disk space
df -h

Monitor Usage

View authentication activity:

cd /opt/docker/decap-oauth
docker compose logs | grep "Auth request"
docker compose logs | grep "Authentication successful"

Count authentications:

docker compose logs | grep "Authentication successful" | wc -l

Update Dependencies

Every 3-6 months:

  1. Update package.json:
cd /opt/docker/decap-oauth
nano package.json

Update versions to latest:

"dependencies": {
  "express": "^4.19.2",      // Check latest
  "cors": "^2.8.5",
  "simple-oauth2": "^5.1.0"  // Check latest
}
  1. Rebuild container:
docker compose down
docker compose build --no-cache
docker compose up -d
  1. Test authentication:
  • Go to your CMS
  • Log in
  • Verify it works

Check for updates:

  • express: https://www.npmjs.com/package/express
  • cors: https://www.npmjs.com/package/cors
  • simple-oauth2: https://www.npmjs.com/package/simple-oauth2

Backup Configuration

Create backup:

cd /opt/docker/decap-oauth

# Create backup directory
mkdir -p ~/backups/decap-oauth

# Backup files (excluding secrets for security)
cp server.js ~/backups/decap-oauth/server.js.backup
cp package.json ~/backups/decap-oauth/package.json.backup
cp Dockerfile ~/backups/decap-oauth/Dockerfile.backup

# Note: Don't backup docker-compose.yml with secrets!
# Instead, store secrets separately and securely

Restore from backup:

cd /opt/docker/decap-oauth
docker compose down

# Restore files
cp ~/backups/decap-oauth/*.backup .

# Manually recreate docker-compose.yml with secrets
nano docker-compose.yml

docker compose up -d

Rotating Secrets

When to rotate:

  • Every 6-12 months (scheduled)
  • If secret may have been compromised
  • When team members leave
  • Before security audits

How to rotate:

  1. Generate new secret on GitHub:
  • https://github.com/settings/developers
  • Find your OAuth App
  • “Generate a new client secret”
  • Copy new secret
  1. Update docker-compose.yml:
cd /opt/docker/decap-oauth
nano docker-compose.yml
# Update OAUTH_CLIENT_SECRET
  1. Restart container:
docker compose restart
  1. Test:
  • Users may need to re-authorize
  • Test authentication flow works

Container Logs

View recent logs:

docker compose logs --tail=50

Follow logs live:

docker compose logs -f

Filter logs:

# Only errors
docker compose logs | grep -i error

# Only auth events
docker compose logs | grep -i auth

# Last hour
docker compose logs --since=1h

Log rotation:

  • Docker automatically rotates logs
  • Default: 10MB max per file, keeps 3 files
  • No manual management needed