---
title: "GitHub Actions"
description: "Automate deployments with Alpacon's GitHub Actions. Learn how to use alpacon-setup, websh, cp, and common actions for secure CI/CD without SSH keys."
---

> Full documentation index: https://docs.alpacax.com/llms.txt

# GitHub Actions integration

Automate deployments and server management with Alpacon's purpose-built GitHub Actions.

## Available actions

Alpacon provides four GitHub Actions for CI/CD workflows:

| Action | Purpose | Use case |
|--------|---------|----------|
| [alpacon-setup-action] | Install Alpacon CLI | Always run first |
| [alpacon-websh-action] | Execute remote commands | Deploy apps, restart services |
| [alpacon-cp-action] | Transfer files | Upload builds, download logs |
| [alpacon-common-action] | Run any Alpacon command | List servers, check status |

For full input details, see [Actions reference](/reference/actions/).

## Quick start

### 1. Create a service token

CI and automation run on service tokens. A service token belongs to an application rather than to a person, so it survives staff changes, and its ACL bounds exactly what the pipeline may do.

1. Go to **IAM** → [applications](/use/iam/applications/) and open the application this pipeline belongs to, or create one.
2. Open the **Tokens** tab and click **Create token**.
3. Name the token and set its expiry, choose its permissions from an access template or one by one, prepare its servers, commands, and paths, and on the review step write the reason it is needed.
4. Copy or download the key when it is shown. It starts with `alpst-`, and it is shown only once.

Before issuing the token, give the application a server account and a global role covering the required scopes. The examples use `ci-deploy`; replace it with that application-owned account. Prepare deployment directories and Docker access for this account. A service token cannot run as `root` or open an interactive Websh session.

On Alpacon Cloud, service-token sudo is allowed below Critical, including unassessed commands, and refused at Critical unless the workspace is Advisory. In Advisory, it is not refused for risk at any level, and the decision is recorded. Outside Advisory, a workspace `token_exec` rule can tighten the risk threshold. The workspace sudo setting must be on; otherwise sudo is refused with `workspace_sudo_with_mfa_disabled`. A default self-hosted installation refuses every service-token sudo attempt. The command rule must match the whole submitted line, including `sudo`. See [Service tokens](/use/iam/applications/service-tokens/).

Command and file-transfer scopes always wait for approval in the web console. Have a Superuser or an approver for the request approve the token before adding ACLs or starting the pipeline.

### 2. Add the ACL rules

A new service token reaches nothing until you add ACL rules. Servers, commands, and file paths are each deny-by-default, so a token with no rules is refused everywhere.

After approval, open the token's **Access rules** tab and apply the prepared rules, or add server and file rules there. On Alpacon Cloud, a **Command ACL admins** member adds commands under **Pre-approved commands** on the same tab. This access is separate from the workspace role. On self-hosted deployments, use command ACLs. Configure:

| Rule | What it bounds |
|--------|-----------------|
| Server ACL | Which servers the pipeline may reach |
| Command access | The whole command submitted by the workflow, for example `cd /opt/myapp && git pull && npm install && pm2 restart app` |
| File ACL | Which paths it may upload to or download from |

A file transfer checks the server rule first, then the file rule. Skip the **Access rules** tab and the workflow fails on its first command.

### 3. Add secrets to GitHub

Add these secrets to your repository (Settings → Secrets → Actions):

```
ALPACON_WORKSPACE_URL: https://your-workspace.us1.alpacon.io
ALPACON_SERVICE_TOKEN: alpst-your-token-here
```

The action input is named `api-token` and takes the service token as-is. The name is kept for compatibility with published workflows.

### 4. Use in workflow

```yaml
name: Deploy with Alpacon
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup Alpacon CLI
        uses: alpacax/alpacon-setup-action@v1

      - name: Deploy to production
        uses: alpacax/alpacon-websh-action@v1
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          target: 'prod-server'
          username: 'ci-deploy'
          script: |
            cd /opt/myapp && git pull && npm install && pm2 restart app
```

## Alternative: using Alpacon CLI container

If you prefer to use the Alpacon CLI directly without the pre-built actions, you can use the official Docker container.

On this path the CLI signs in to Alpacon Cloud with the workspace name and region rather than a URL, so add two more secrets:

```
ALPACON_WORKSPACE: your-workspace
ALPACON_REGION: us1
```

`ALPACON_WORKSPACE_URL` stays as it is, feeding the `workspace-url` input on the four actions; `ALPACON_SERVICE_TOKEN` serves both paths.

```yaml
name: Deploy with Alpacon CLI
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    container:
      image: alpacax/alpacon-cli:latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Login to Alpacon
        run: alpacon login --workspace ${{ secrets.ALPACON_WORKSPACE }} --region ${{ secrets.ALPACON_REGION }} -t ${{ secrets.ALPACON_SERVICE_TOKEN }}

      - name: Deploy to production
        run: |
          alpacon exec ci-deploy@prod-server "cd /opt/myapp && git pull && npm install && pm2 restart app"

      - name: Upload build artifacts
        run: |
          alpacon cp -r ./dist/ ci-deploy@prod-server:/var/www/app/
```

This approach provides direct CLI access with minimal setup and is useful for complex workflows requiring multiple CLI commands.

## Usage examples

> All examples below assume `alpacon-setup-action` has already run in an earlier step. See the [quick start](#quick-start) section for a complete workflow.

### Restart services with sudo

Run as the application's account. Cloud permits sudo below Critical unless a stricter workspace rule applies; Advisory does not refuse it for risk at any level. The workspace sudo setting must be on. Default self-hosted installations refuse it. The command rule must match the whole `sudo systemctl restart nginx` line.

```yaml
- name: Restart nginx
  uses: alpacax/alpacon-websh-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    target: 'web-server'
    username: 'ci-deploy'
    script: sudo systemctl restart nginx
```

### Run as the application account

```yaml
- name: Deploy as the application account
  uses: alpacax/alpacon-websh-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    target: 'web-server'
    username: 'ci-deploy'
    script: |
      cd /home/ci-deploy/app && git pull && npm install
```

A service token always uses its account's own private group.

### Pass environment variables

Restarting a system service requires sudo for this account. See [Restart services with sudo](#restart-services-with-sudo) for the workspace settings and command rule.

```yaml
- name: Deploy with environment variables
  uses: alpacax/alpacon-websh-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    target: 'prod-server'
    username: 'ci-deploy'
    env: |
      APP_ENV=production
      DB_HOST=localhost
    script: |
      echo "Deploying to $APP_ENV"
      sudo systemctl restart myapp
```

### Upload build artifacts

```yaml
- name: Build application
  run: npm run build

- name: Upload build to server
  uses: alpacax/alpacon-cp-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    source: './dist/'
    target-server: 'prod-server'
    username: 'ci-deploy'
    target-path: '/var/www/app/'
    recursive: true
```

### Upload multiple files

```yaml
- name: Upload config files
  uses: alpacax/alpacon-cp-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    source: |
      ./docker-compose.yml
      ./nginx.conf
      ./.env.production
    target-server: 'prod-server'
    username: 'ci-deploy'
    target-path: '/opt/myapp/'
```

### Download logs

```yaml
- name: Download error logs
  uses: alpacax/alpacon-cp-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    source: '/var/log/app/error.log'
    target-server: 'prod-server'
    username: 'ci-deploy'
    target-path: './logs/'
    mode: download

- name: Upload logs as artifact
  uses: actions/upload-artifact@v4
  with:
    name: error-logs
    path: ./logs/
```

### Multi-server deployment

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        server: [web1, web2, web3]
    steps:
      - uses: actions/checkout@v4

      - name: Setup Alpacon CLI
        uses: alpacax/alpacon-setup-action@v1

      - name: Deploy to ${{ matrix.server }}
        uses: alpacax/alpacon-websh-action@v1
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          target: ${{ matrix.server }}
          username: 'ci-deploy'
          script: |
            cd /opt/myapp && git pull && npm install && pm2 restart app
```

### Run any Alpacon command

```yaml
- name: List all servers
  uses: alpacax/alpacon-common-action@v1
  with:
    workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
    api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
    command: "server ls"
```

### Docker deployment with environment selection

Deploy containerized applications to different environments with dynamic server selection:

```yaml
name: Deploy Application

on:
  workflow_dispatch:
    inputs:
      env:
        description: "Deployment environment"
        required: true
        type: choice
        options:
          - dev
          - prod

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Select target server
        run: |
          if [ "${{ inputs.env }}" = "prod" ]; then
            echo "TARGET_SERVER=prod-server" >> $GITHUB_ENV
            echo "APP_PORT=8080" >> $GITHUB_ENV
          else
            echo "TARGET_SERVER=dev-server" >> $GITHUB_ENV
            echo "APP_PORT=8081" >> $GITHUB_ENV
          fi

      - name: Prepare environment file
        env:
          APP_SECRETS: ${{ secrets.APP_SECRETS }}
        run: |
          echo "APP_ENV=${{ inputs.env }}" > /tmp/.env
          echo "APP_PORT=${{ env.APP_PORT }}" >> /tmp/.env
          printf '%s' "$APP_SECRETS" >> /tmp/.env

      - name: Setup Alpacon CLI
        uses: alpacax/alpacon-setup-action@v1

      - name: Docker login on target server
        uses: alpacax/alpacon-websh-action@v1
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
          DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          target: ${{ env.TARGET_SERVER }}
          username: 'ci-deploy'
          env: |
            DOCKER_USERNAME
            DOCKER_PASSWORD
          script: |
            printf '%s\n' "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin

      - name: Copy docker-compose.yml
        uses: alpacax/alpacon-cp-action@v1
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          source: ./docker-compose.yml
          target-server: ${{ env.TARGET_SERVER }}
          username: 'ci-deploy'
          target-path: /opt/myapp/docker-compose.yml

      - name: Copy environment file
        uses: alpacax/alpacon-cp-action@v1
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          source: /tmp/.env
          target-server: ${{ env.TARGET_SERVER }}
          username: 'ci-deploy'
          target-path: /opt/myapp/.env

      - name: Deploy application
        uses: alpacax/alpacon-websh-action@v1
        with:
          workspace-url: ${{ secrets.ALPACON_WORKSPACE_URL }}
          api-token: ${{ secrets.ALPACON_SERVICE_TOKEN }}
          target: ${{ env.TARGET_SERVER }}
          username: 'ci-deploy'
          script: |
            docker compose -f /opt/myapp/docker-compose.yml pull
            docker compose -f /opt/myapp/docker-compose.yml --env-file /opt/myapp/.env up -d
            docker compose -f /opt/myapp/docker-compose.yml ps
```

## Best practices

- Create dedicated tokens for CI/CD with minimal permissions
- Use pre-approved commands on Cloud or command ACLs on self-hosted deployments to restrict token commands
- Store tokens as secrets—never commit them to your repository
- Rotate tokens regularly in workspace, then update GitHub secrets
- Use organization secrets for tokens shared across repositories
- All commands are recorded in workspace audit logs with sensitive values automatically masked

## Resources

- [Actions reference](/reference/actions/)
- [Alpacon CLI documentation](/reference/cli/)
- [GitHub Marketplace - Setup Action](https://github.com/marketplace/actions/setup-alpacon-cli)
- [GitHub Marketplace - Websh Action](https://github.com/marketplace/actions/alpacon-websh-action)
- [GitHub Marketplace - CP Action](https://github.com/marketplace/actions/alpacon-cp-action)
- [GitHub Marketplace - Common Action](https://github.com/marketplace/actions/alpacon-common-action)

[alpacon-setup-action]: /reference/actions/setup/
[alpacon-websh-action]: /reference/actions/websh/
[alpacon-cp-action]: /reference/actions/cp/
[alpacon-common-action]: /reference/actions/common/

## Next steps

- [GitLab CI integration](/integrate/gitlab-ci/)
- [Jenkins integration](/integrate/jenkins/)
- [CLI reference](/reference/cli/)

Source: https://docs.alpacax.com/integrate/github-actions/
