Workflows move to .gitea/workflows, which Gitea prefers over .github/workflows when both exist. GitHub Actions can only resolve `uses:` against github.com, so the README now spells out both forms: the full Gitea URL, and the checkout-then-`uses: ./` fallback for a GitHub workflow that has no mirror of this repository.
351 lines
12 KiB
Markdown
351 lines
12 KiB
Markdown
# dokploy-deploy-action
|
|
|
|
Trigger a [Dokploy](https://dokploy.com) deployment from a CI job and wait for
|
|
it to finish. Works on **GitHub Actions** and **Gitea Actions**.
|
|
|
|
Dokploy's built-in Git integration deploys on every push, before anything has
|
|
had a chance to fail. This action makes the deployment an ordinary job, so it
|
|
can sit behind your linters and tests, deploy the exact image you just built,
|
|
and fail the run when the deployment fails.
|
|
|
|
```yaml
|
|
- uses: https://gitea.coolify.vojtkov.dev/usr_unknown/dokploy-deploy-action@v1
|
|
with:
|
|
host: ${{ secrets.DOKPLOY_HOST }}
|
|
api-key: ${{ secrets.DOKPLOY_API_KEY }}
|
|
project: shop
|
|
service: api
|
|
docker-image: ghcr.io/acme/api:${{ github.sha }}
|
|
```
|
|
|
|
## Contents
|
|
|
|
- [Why not the built-in webhook?](#why-not-the-built-in-webhook)
|
|
- [Quick start](#quick-start)
|
|
- [Referencing the action](#referencing-the-action)
|
|
- [Choosing the service](#choosing-the-service)
|
|
- [Deploying a new image](#deploying-a-new-image)
|
|
- [Compose stacks](#compose-stacks)
|
|
- [Other actions](#other-actions)
|
|
- [Inputs](#inputs)
|
|
- [Outputs](#outputs)
|
|
- [How it decides the deployment failed](#how-it-decides-the-deployment-failed)
|
|
- [Troubleshooting](#troubleshooting)
|
|
- [Development](#development)
|
|
- [Related](#related)
|
|
|
|
## Why not the built-in webhook?
|
|
|
|
Dokploy's webhook is the right tool when a push *is* the deployment. It stops
|
|
being the right tool as soon as you want something between the two:
|
|
|
|
| | Webhook | This action |
|
|
| --- | --- | --- |
|
|
| Runs after your tests | no | yes |
|
|
| Deploys an image built in CI | no | yes, via `docker-image` |
|
|
| Fails the CI run on a bad deploy | no | yes |
|
|
| Build log in the CI job | no | yes, on failure or always |
|
|
| Deploy on a tag, on dispatch, on a schedule | no | yes |
|
|
| Promote the same artifact to staging then production | no | yes |
|
|
|
|
## Quick start
|
|
|
|
1. In Dokploy, go to **Settings → Profile → API/CLI** and create a token.
|
|
2. Add two repository secrets: `DOKPLOY_HOST` (e.g.
|
|
`https://dokploy.example.com`) and `DOKPLOY_API_KEY`.
|
|
3. Add a deploy job.
|
|
|
|
```yaml
|
|
jobs:
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- run: npm ci && npm test
|
|
|
|
deploy:
|
|
needs: test
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: https://gitea.coolify.vojtkov.dev/usr_unknown/dokploy-deploy-action@v1
|
|
with:
|
|
host: ${{ secrets.DOKPLOY_HOST }}
|
|
api-key: ${{ secrets.DOKPLOY_API_KEY }}
|
|
project: shop
|
|
service: api
|
|
```
|
|
|
|
`host` and `api-key` may also come from `DOKPLOY_HOST` and `DOKPLOY_API_KEY` in
|
|
the environment, which is handy when a whole job talks to Dokploy:
|
|
|
|
```yaml
|
|
env:
|
|
DOKPLOY_HOST: ${{ secrets.DOKPLOY_HOST }}
|
|
DOKPLOY_API_KEY: ${{ secrets.DOKPLOY_API_KEY }}
|
|
```
|
|
|
|
Worked examples, from lint through image build to deploy:
|
|
|
|
- [`examples/github-build-and-deploy.yml`](examples/github-build-and-deploy.yml)
|
|
- [`examples/gitea-build-and-deploy.yml`](examples/gitea-build-and-deploy.yml)
|
|
- [`examples/manual-promote.yml`](examples/manual-promote.yml) — promote or roll
|
|
back an existing tag from the Actions tab.
|
|
|
|
## Referencing the action
|
|
|
|
The action is plain JavaScript with no dependencies and no bundled `dist/`, so
|
|
a runner executes it straight from this repository — nothing to build, nothing
|
|
to vendor. It also reads only `GITHUB_*` variables, which Gitea sets exactly as
|
|
GitHub does, so the same step works on either.
|
|
|
|
**From Gitea** — the full URL always works, whatever your instance's
|
|
`DEFAULT_ACTIONS_URL` is set to:
|
|
|
|
```yaml
|
|
- uses: https://gitea.coolify.vojtkov.dev/usr_unknown/dokploy-deploy-action@v1
|
|
```
|
|
|
|
If your instance sets `DEFAULT_ACTIONS_URL = self`, the short form works too:
|
|
|
|
```yaml
|
|
- uses: usr_unknown/dokploy-deploy-action@v1
|
|
```
|
|
|
|
**From GitHub** — GitHub Actions can only resolve `uses:` against github.com;
|
|
it will not fetch an action from another host. So a GitHub workflow needs
|
|
either a mirror of this repository on github.com, or a local checkout:
|
|
|
|
```yaml
|
|
- uses: actions/checkout@v4
|
|
with:
|
|
repository: your-org/dokploy-deploy-action
|
|
ref: v1
|
|
path: .actions/dokploy-deploy
|
|
- uses: ./.actions/dokploy-deploy
|
|
with:
|
|
project: shop
|
|
service: api
|
|
```
|
|
|
|
[`examples/github-build-and-deploy.yml`](examples/github-build-and-deploy.yml)
|
|
is written in the short form, on the assumption that you have mirrored this
|
|
repository to github.com.
|
|
|
|
## Choosing the service
|
|
|
|
Either name the service, or give an id.
|
|
|
|
```yaml
|
|
# By name. `environment` defaults to the project's default environment.
|
|
project: shop
|
|
service: api
|
|
environment: staging
|
|
```
|
|
|
|
`service` matches either the name shown in the UI or the underlying Docker
|
|
service name, case-insensitively. If the name is wrong the error lists what
|
|
does exist in that environment, which is usually enough to fix it.
|
|
|
|
```yaml
|
|
# By id — from the Dokploy URL, or from a Terraform/Pulumi output.
|
|
application-id: kJ3n8xQ2vB
|
|
```
|
|
|
|
Ids never go stale in the way names do, but they change when a service is
|
|
recreated. Names read better in a workflow file; ids are better when the id is
|
|
already an output of whatever created the service.
|
|
|
|
## Deploying a new image
|
|
|
|
`docker-image` points the application at a tag and then deploys it. This is the
|
|
whole reason to run a deployment from CI: the image that gets deployed is the
|
|
one your tests just passed against.
|
|
|
|
```yaml
|
|
- uses: https://gitea.coolify.vojtkov.dev/usr_unknown/dokploy-deploy-action@v1
|
|
with:
|
|
project: shop
|
|
service: api
|
|
docker-image: ghcr.io/acme/api:${{ needs.build.outputs.tag }}
|
|
registry-url: ghcr.io
|
|
registry-username: ${{ github.actor }}
|
|
registry-password: ${{ secrets.GHCR_PULL_TOKEN }}
|
|
```
|
|
|
|
Two things to know:
|
|
|
|
- This switches the application's source type to **Docker**. An application
|
|
Dokploy builds from Git should not be given a `docker-image`; use `redeploy`
|
|
instead and let Dokploy build it.
|
|
- Dokploy **stores** the registry credentials and reuses them every time it
|
|
restarts the container. A job-scoped token like `GITHUB_TOKEN` will work for
|
|
the deployment and then stop working, so use a long-lived token — or
|
|
configure a registry in Dokploy once and leave the credentials out here.
|
|
|
|
Always deploy an immutable tag (a commit SHA or a digest) rather than `latest`.
|
|
With `latest`, nothing about the application changes between deploys, and what
|
|
you get depends on the node's pull policy.
|
|
|
|
## Compose stacks
|
|
|
|
Same thing, with `compose-id` or a `service` that names a stack. Images come
|
|
from the Compose file, so `docker-image` does not apply:
|
|
|
|
```yaml
|
|
- uses: https://gitea.coolify.vojtkov.dev/usr_unknown/dokploy-deploy-action@v1
|
|
with:
|
|
project: shop
|
|
service: worker
|
|
action: redeploy
|
|
```
|
|
|
|
## Other actions
|
|
|
|
`action` selects the operation. `deploy` and `redeploy` queue a build and are
|
|
followed to completion; the rest are immediate.
|
|
|
|
| `action` | Effect |
|
|
| --- | --- |
|
|
| `deploy` (default) | Build and deploy. |
|
|
| `redeploy` | Rebuild and deploy, without changing the source. |
|
|
| `start` | Start a stopped service. |
|
|
| `stop` | Stop a running service. |
|
|
| `reload` | Recreate the container from the current image. Applications only. |
|
|
|
|
## Inputs
|
|
|
|
Everything is optional; the required combinations are described above.
|
|
|
|
### Connection
|
|
|
|
| Input | Default | Description |
|
|
| --- | --- | --- |
|
|
| `host` | `$DOKPLOY_HOST` | Instance URL, e.g. `https://dokploy.example.com`. A trailing `/api` is tolerated. |
|
|
| `api-key` | `$DOKPLOY_API_KEY` | Token from Settings → Profile → API/CLI. |
|
|
| `insecure` | `false` | Skip TLS verification. Self-signed certificates only. |
|
|
| `request-timeout` | `60` | Seconds before a single API request is abandoned. |
|
|
|
|
### Target
|
|
|
|
| Input | Default | Description |
|
|
| --- | --- | --- |
|
|
| `application-id` | | Application to act on. |
|
|
| `compose-id` | | Compose stack to act on. |
|
|
| `project` | | Project name, for lookup by name. |
|
|
| `service` | | Application or stack name, for lookup by name. |
|
|
| `environment` | project default | Environment name. |
|
|
|
|
### Deployment
|
|
|
|
| Input | Default | Description |
|
|
| --- | --- | --- |
|
|
| `action` | `deploy` | `deploy`, `redeploy`, `start`, `stop` or `reload`. |
|
|
| `docker-image` | | Image to deploy. Applications only. |
|
|
| `registry-username` | | Registry username, alongside `docker-image`. |
|
|
| `registry-password` | | Registry password or token. Masked in the log. |
|
|
| `registry-url` | | Registry URL. |
|
|
| `title` | short commit SHA | Deployment title shown in Dokploy. |
|
|
| `description` | repo, ref, actor, run link | Deployment description. |
|
|
|
|
### Waiting
|
|
|
|
| Input | Default | Description |
|
|
| --- | --- | --- |
|
|
| `wait` | `true` | Follow the deployment and fail the step if it errors. |
|
|
| `timeout` | `600` | Seconds to wait for the deployment to finish. |
|
|
| `poll-interval` | `5` | Seconds between status checks. |
|
|
| `cancel-on-timeout` | `false` | Ask Dokploy to cancel the build when the timeout is hit. |
|
|
| `logs` | `on-failure` | `on-failure`, `always` or `never`. |
|
|
| `log-tail` | `200` | Number of log lines to print. |
|
|
|
|
## Outputs
|
|
|
|
| Output | Description |
|
|
| --- | --- |
|
|
| `status` | `done`, `error`, `cancelled`, `timed-out`, or `triggered` when `wait: false`. |
|
|
| `succeeded` | `"true"` or `"false"`. |
|
|
| `deployment-id` | The deployment this run started. |
|
|
| `service-kind` | `application` or `compose`. |
|
|
| `service-id` | Resolved service id. |
|
|
| `application-id` | Resolved application id; empty for a stack. |
|
|
| `compose-id` | Resolved stack id; empty for an application. |
|
|
| `app-name` | Docker service name. |
|
|
| `duration` | Seconds spent triggering and waiting. |
|
|
|
|
## How it decides the deployment failed
|
|
|
|
`application.deploy` is fire-and-forget: it puts a job on Dokploy's queue and
|
|
returns immediately. Reporting on that alone would mean every deployment
|
|
"succeeds". So the step:
|
|
|
|
1. Lists the service's existing deployments.
|
|
2. Triggers the action.
|
|
3. Polls until a deployment appears that was not in step 1, and follows it to
|
|
`done`, `error` or `cancelled`.
|
|
|
|
Because the snapshot is taken first, a deployment somebody else started is
|
|
never mistaken for this one. `done` passes; anything else fails the step and
|
|
prints the build log.
|
|
|
|
If `timeout` elapses first, the step fails with `status: timed-out` and the
|
|
build keeps running on Dokploy — set `cancel-on-timeout: true` to stop it
|
|
instead. Give `timeout` room: it covers the queue wait as well as the build.
|
|
|
|
Set `wait: false` to go back to fire-and-forget. The step then reports
|
|
`triggered` and cannot tell you whether the deployment worked.
|
|
|
|
## Troubleshooting
|
|
|
|
**`Host must start with http:// or https://`** — `host` needs a scheme.
|
|
|
|
**`dokploy API error (HTTP 401) UNAUTHORIZED`** — the token is wrong, expired,
|
|
or belongs to a different organization than the project.
|
|
|
|
**`... returned a non-JSON response; is host pointing at a Dokploy instance?`**
|
|
— a reverse proxy answered instead of Dokploy. Check the host, and that `/api`
|
|
is reachable from the runner.
|
|
|
|
**`No deployment appeared within Ns`** — the trigger was accepted but no build
|
|
started. Usually Dokploy's queue is stuck or the target server is unreachable;
|
|
check the service in the UI.
|
|
|
|
**A self-hosted Dokploy behind a private network** — the runner needs to reach
|
|
it. Either use a self-hosted runner or expose the API.
|
|
|
|
**A self-signed certificate** — `insecure: true`. It disables verification for
|
|
the whole step, so prefer a real certificate.
|
|
|
|
## Development
|
|
|
|
No dependencies, no build step, no committed `dist/`. Node 20 or newer.
|
|
|
|
```bash
|
|
node --test # 47 unit tests, no network
|
|
```
|
|
|
|
`src/client.js` is the API client, `src/resolve.js` turns names into ids,
|
|
`src/deploy.js` triggers and follows a deployment, `src/core.js` is a small
|
|
stand-in for `@actions/core`, and `src/index.js` wires them together. The
|
|
action is deliberately dependency-free so both runners can execute it straight
|
|
from the repository — CI enforces that.
|
|
|
|
Releases: tag `vX.Y.Z`, and the release workflow moves the `vX` tag that
|
|
workflows pin.
|
|
|
|
## Related
|
|
|
|
Same Dokploy API, different jobs:
|
|
|
|
- [terraform-provider-dokploy](https://gitea.coolify.vojtkov.dev/usr_unknown/terraform-provider-dokploy)
|
|
— declare projects, applications, databases and domains.
|
|
- [pulumi-dokploy](https://gitea.coolify.vojtkov.dev/usr_unknown/pulumi-dokploy) — the same, in
|
|
TypeScript, Python, Go or .NET.
|
|
|
|
The usual division of labour: the provider creates the service and owns its
|
|
configuration, this action deploys code into it. Pass the provider's
|
|
`application_id` output straight into `application-id` here.
|
|
|
|
## License
|
|
|
|
[MPL-2.0](LICENSE).
|