Deploy Dokploy applications from GitHub and Gitea Actions
A dependency-free node20 action that triggers application.deploy or compose.deploy, then follows the deployment to a terminal status so a failed build fails the CI run. Snapshots the deployment list before triggering, so a concurrent deployment is never mistaken for this one. Optionally points the application at a freshly built image first, which is what lets a workflow lint, test, push and deploy the same artifact.
This commit is contained in:
330
README.md
Normal file
330
README.md
Normal file
@@ -0,0 +1,330 @@
|
||||
# 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: maxvojtkov/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)
|
||||
- [Gitea](#gitea)
|
||||
- [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: maxvojtkov/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.
|
||||
|
||||
## Gitea
|
||||
|
||||
Nothing changes. The action is plain JavaScript with no dependencies and no
|
||||
bundled `dist/`, so Gitea's `act_runner` executes it straight from the
|
||||
repository, and Gitea sets the same `GITHUB_*` variables the defaults are built
|
||||
from.
|
||||
|
||||
How you reference it depends on your instance's `DEFAULT_ACTIONS_URL`. If it is
|
||||
the default (`https://github.com`), the short form works:
|
||||
|
||||
```yaml
|
||||
- uses: maxvojtkov/dokploy-deploy-action@v1
|
||||
```
|
||||
|
||||
Otherwise give the full URL, or mirror this repository into your instance:
|
||||
|
||||
```yaml
|
||||
- uses: https://github.com/maxvojtkov/dokploy-deploy-action@v1
|
||||
```
|
||||
|
||||
## 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: maxvojtkov/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: maxvojtkov/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://github.com/maxvojtkov/terraform-provider-dokploy)
|
||||
— declare projects, applications, databases and domains.
|
||||
- [pulumi-dokploy](https://github.com/maxvojtkov/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).
|
||||
Reference in New Issue
Block a user