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.
dokploy-deploy-action
Trigger a Dokploy 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.
- 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?
- Quick start
- Referencing the action
- Choosing the service
- Deploying a new image
- Compose stacks
- Other actions
- Inputs
- Outputs
- How it decides the deployment failed
- Troubleshooting
- Development
- 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
- In Dokploy, go to Settings → Profile → API/CLI and create a token.
- Add two repository secrets:
DOKPLOY_HOST(e.g.https://dokploy.example.com) andDOKPLOY_API_KEY. - Add a deploy job.
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:
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.ymlexamples/gitea-build-and-deploy.ymlexamples/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:
- 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:
- 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:
- 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
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.
# 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.
# 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.
- 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; useredeployinstead 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_TOKENwill 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:
- 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:
- Lists the service's existing deployments.
- Triggers the action.
- Polls until a deployment appears that was not in step 1, and follows it to
done,errororcancelled.
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.
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 — declare projects, applications, databases and domains.
- 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.