Skip to content

Replace Dokploy with Docker Compose + Traefik + docker-rollout - #837

Merged
Asherlc merged 4 commits into
mainfrom
Asherlc/explore-dokploy-alt
Apr 11, 2026
Merged

Asherlc merged 4 commits into
mainfrom
Asherlc/explore-dokploy-alt

Conversation

@Asherlc

@Asherlc Asherlc commented Apr 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • Removes Dokploy PaaS layer, replacing with direct Docker Compose + standalone Traefik + docker-rollout for zero-downtime deploys
  • Adds Portainer CE (portainer.dofek.asherlc.com) and Netdata (netdata.dofek.asherlc.com) for Docker management and server health monitoring, both behind Authentik forward auth
  • Rewrites all deploy workflows to use SSH + docker rollout instead of Terraform + Dokploy API
  • Secrets now flow from Infisical CLI directly to /opt/dofek/.env.prod on the server, eliminating the Terraform templatefile secret injection
  • Simplifies dev compose to just backing services (db, redis, collector) on the default network

Test plan

  • Verify terraform validate passes for deploy/server/ and deploy/db/
  • Verify deploy/docker-compose.deploy.yml syntax: docker compose -f deploy/docker-compose.deploy.yml config
  • Provision a test server with cd deploy/server && terraform apply
  • Deploy full stack and verify all domains resolve with valid TLS
  • Test zero-downtime rollout: docker rollout web with concurrent requests
  • Verify Portainer and Netdata UIs are accessible and Authentik auth works
  • Verify dev compose: docker compose up -d starts db, redis, collector

🤖 Generated with Claude Code

Remove the Dokploy PaaS layer and replace with direct Docker Compose
management. Traefik handles TLS via Cloudflare DNS challenge,
docker-rollout provides zero-downtime deploys, and GitHub Actions
deploys via SSH instead of the Dokploy API.

New services: Portainer (portainer.dofek.asherlc.com) for Docker
management, Netdata (netdata.dofek.asherlc.com) for server health
monitoring — both behind Authentik forward auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 11, 2026 00:02
@github-actions

github-actions Bot commented Apr 11, 2026 •

Copy link
Copy Markdown
Contributor

Storybook preview for 8813a3b6 is ready: Open Storybook

This comment updates automatically on each PR push.

@github-actions

github-actions Bot commented Apr 11, 2026 •

Copy link
Copy Markdown
Contributor

Mobile Preview

Scan to open on device:

QR code for dofek://preview/pr-837

Channel pr-837
Deep Link dofek://preview/pr-837
Commit 8813a3b

To test on device:

  1. Build and install the preview client: PREVIEW_CHANNEL=pr-837 npx expo prebuild --clean -p ios
  2. Or tap deep link on an existing preview build: dofek://preview/pr-837

Each PR gets its own channel. Build a preview client with PREVIEW_CHANNEL=pr-{N} to test.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the Dokploy-based deployment approach with a direct Docker Compose production stack fronted by Traefik and deployed via SSH-driven docker-rollout, while also adding Portainer and Netdata behind Authentik forward-auth.

Changes:

  • Remove Dokploy provisioning/config tooling (Terraform modules, setup scripts, secret sync script/tests) and update docs accordingly.
  • Add a unified production Compose stack (deploy/docker-compose.deploy.yml) with Traefik, app services, infra services, backups, and management UIs (Portainer/Netdata).
  • Rewrite GitHub deploy + secret-sync workflows to SSH into the server, export Infisical secrets to /opt/dofek/.env.prod, and deploy/restart via Docker Compose / rollout.

Reviewed changes

Copilot reviewed 26 out of 28 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
scripts/sync-dokploy-env-from-infisical.ts Removed Dokploy env sync script (no longer needed).
scripts/sync-dokploy-env-from-infisical.test.ts Removed tests for Dokploy env sync logic.
scripts/sync-dokploy-env-from-infisical-lib.ts Removed Dokploy env merge/parsing library.
README.md Updated infrastructure/deploy documentation from Dokploy → Compose/Traefik/rollout.
docker-compose.yml Simplified dev compose to backing services only.
deploy/terraform.tfvars.example Updated example tfvars to match new server module inputs.
deploy/server/main.tf Updated Hetzner provisioning to install Docker + rollout + Infisical (no Dokploy).
deploy/server/cloud-init.yml New cloud-init for Docker/rollout/Infisical installation and host setup.
deploy/otel-collector-config.yaml Added/updated collector config for OTLP + Docker filelog shipping.
deploy/dokploy/terraform.tfvars.example Removed Dokploy tfvars example.
deploy/dokploy/setup.sh Removed Dokploy setup script (tRPC-based).
deploy/dokploy/managed-secret-keys.json Removed Dokploy-managed secret allowlist.
deploy/dokploy/infra-compose.yml Removed Dokploy infra compose stack template.
deploy/dokploy/cloud-init.yml Removed Dokploy installer cloud-init.
deploy/dokploy/.gitignore Removed Dokploy-specific gitignore.
deploy/dokploy-compose/main.tf Removed Terraform template renderer for Dokploy infra compose.
deploy/dokploy-app/main.tf Removed Terraform-based Dokploy app deploy module.
deploy/docker-compose.deploy.yml New production Compose stack with Traefik, apps, infra, backups, Portainer, Netdata.
deploy/db/main.tf New Terraform module for managing the TimescaleDB extension via SSH tunnel.
deploy/cloudflare/main.tf Added DNS records for Portainer/Netdata; updated comments/outputs.
AGENTS.md Updated “no manual server changes” guidance to reflect Compose/Terraform workflow.
.github/workflows/secret-sync.yml Reworked secret sync to SSH + infisical export → /opt/dofek/.env.prod + restarts.
.github/workflows/preview-deploy.yml Removed Dokploy-based preview deploy workflow.
.github/workflows/deploy.yml Updated cleanup to SSH into server and prune images.
.github/workflows/deploy-worker.yml Updated worker deploy to SSH + pull + rollout-based deploy.
.github/workflows/deploy-web.yml Updated web deploy to SSH + pull + rollout-based deploy.
.github/workflows/deploy-infra-compose.yml Updated infra deploy to scp compose/config + docker compose pull/up + DB extension apply.
.github/workflows/ci.yml Updated infra validation + change-detection triggers for new deploy layout.
Comments suppressed due to low confidence (2)

deploy/server/main.tf:103

  • user_data is rendered from cloud-init.yml with ghcr_token and will end up stored in Terraform state (and potentially visible via Hetzner/user-data APIs). Avoid embedding registry PATs in cloud-init/Terraform state; instead perform docker login during deploy using ephemeral credentials, or use a pull token stored on the host outside Terraform state (e.g., created manually and referenced via file/1Password/Infisical on the server).
    docker-compose.yml:46
  • This dev compose mounts /opt/dofek/otel-collector-config.yaml from the host, which typically won’t exist on developer machines and can cause the collector container to fail to start (Docker will mount a directory instead of the file). For local dev, mount the repo’s deploy/otel-collector-config.yaml (or make the mount optional) so docker compose up -d works as documented.
  collector:
    image: otel/opentelemetry-collector-contrib:0.149.0
    restart: unless-stopped
    user: "0:0"
    environment:
      AXIOM_API_TOKEN: ${AXIOM_API_TOKEN}
      SENTRY_OTLP_LOGS_ENDPOINT: ${SENTRY_OTLP_LOGS_ENDPOINT:-}
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /opt/dofek/otel-collector-config.yaml:/etc/otelcol-contrib/config.yaml:ro
    healthcheck:

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +44 to +49
- mkdir -p /usr/local/lib/docker/cli-plugins
- |
ARCH=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/amd64/')
curl -fsSL "https://github.com/Wowu/docker-rollout/releases/latest/download/docker-rollout-linux-$${ARCH}" \
-o /usr/local/lib/docker/cli-plugins/docker-rollout
- chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docker-rollout plugin is fetched from the releases/latest URL without any version pinning or checksum/signature verification. For reproducible and safer provisioning, pin a specific docker-rollout version and verify the downloaded binary (e.g., sha256) before installing.

Suggested change
- mkdir -p /usr/local/lib/docker/cli-plugins
- |
ARCH=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/amd64/')
curl -fsSL "https://github.com/Wowu/docker-rollout/releases/latest/download/docker-rollout-linux-$${ARCH}" \
-o /usr/local/lib/docker/cli-plugins/docker-rollout
- chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout
- |
set -eu
mkdir -p /usr/local/lib/docker/cli-plugins
ARCH=$(uname -m | sed 's/aarch64/arm64/;s/x86_64/amd64/')
DOCKER_ROLLOUT_VERSION="v0.6.0"
case "$${ARCH}" in
amd64)
DOCKER_ROLLOUT_SHA256="REPLACE_WITH_OFFICIAL_AMD64_SHA256"
;;
arm64)
DOCKER_ROLLOUT_SHA256="REPLACE_WITH_OFFICIAL_ARM64_SHA256"
;;
*)
echo "Unsupported architecture: $${ARCH}" >&2
exit 1
;;
esac
curl -fsSL "https://github.com/Wowu/docker-rollout/releases/download/$${DOCKER_ROLLOUT_VERSION}/docker-rollout-linux-$${ARCH}" \
-o /usr/local/lib/docker/cli-plugins/docker-rollout
echo "$${DOCKER_ROLLOUT_SHA256} /usr/local/lib/docker/cli-plugins/docker-rollout" | sha256sum -c -
chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Pinned to v0.13 (it's a shell script, not a compiled binary, so no checksum needed — but version pinning is still important for reproducibility).

Comment thread deploy/server/cloud-init.yml Outdated
- chmod +x /usr/local/lib/docker/cli-plugins/docker-rollout

# Install Infisical CLI
- curl -1sLf 'https://artifacts.infisical.com/repos/apt/setup.deb.sh' | bash

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Infisical is installed via a remote setup.deb.sh | bash pipeline. This makes server provisioning non-reproducible and increases supply-chain risk; prefer adding Infisical’s apt repo key + source explicitly (or pin a .deb version) rather than executing a remote script.

Suggested change
- curl -1sLf 'https://artifacts.infisical.com/repos/apt/setup.deb.sh' | bash
- install -m 0755 -d /etc/apt/keyrings
- curl -fsSL https://artifacts.infisical.com/gpg/infisical-archive-keyring.asc -o /etc/apt/keyrings/infisical.asc
- chmod a+r /etc/apt/keyrings/infisical.asc
- echo "deb [signed-by=/etc/apt/keyrings/infisical.asc] https://artifacts.infisical.com/repos/apt $(. /etc/os-release && echo $VERSION_CODENAME) main" > /etc/apt/sources.list.d/infisical.list
- apt-get update

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Switched to explicit GPG key + apt source list, matching the Docker install pattern above it.

Comment thread deploy/server/cloud-init.yml Outdated
Comment on lines +58 to +59
# GHCR login for image pulls
- echo "${ghcr_token}" | docker login ghcr.io -u "${ghcr_username}" --password-stdin

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logging into GHCR in cloud-init using a PAT risks leaking the token via cloud-init logs (/var/log/cloud-init-output.log) and makes rotation harder. Consider moving registry login to the deploy workflow (ssh step) or storing credentials on the host in a root-only Docker config created out-of-band (not in user_data).

Suggested change
# GHCR login for image pulls
- echo "${ghcr_token}" | docker login ghcr.io -u "${ghcr_username}" --password-stdin
# Do not log into GHCR from cloud-init: perform registry authentication
# during deployment (for example, in the SSH deploy step) or provide a
# root-only Docker config out-of-band so credentials are not embedded in
# user-data or exposed via cloud-init logs.

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Removed GHCR login from cloud-init entirely. Registry auth now happens during deploy workflows via SSH, keeping credentials out of user-data, cloud-init logs, and Terraform state.

Comment thread .github/workflows/secret-sync.yml Outdated
Comment on lines +41 to +49
ssh "root@${SERVER_HOST}" bash -s -- "$INFISICAL_TOKEN" "$DRY_RUN" <<'SYNC'
set -euo pipefail
INFISICAL_TOKEN="$1"
DRY_RUN="$2"

# Export secrets from Infisical to .env file
infisical export --env=prod --format=dotenv \
--token="$INFISICAL_TOKEN" \
> /opt/dofek/.env.prod.new

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Infisical token is passed to the server as an SSH argument and then used via --token=.... Both patterns can expose the token in process listings / audit logs on the server. Prefer using INFISICAL_TOKEN as an environment variable (if supported by the CLI) and avoid passing secrets as positional args to ssh (e.g., send via stdin or use pre-provisioned server-side auth).

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Now using ssh -o SendEnv=INFISICAL_TOKEN so the token is passed as an environment variable rather than a positional argument. The Infisical CLI reads it from the env automatically.

Comment thread deploy/docker-compose.deploy.yml Outdated
container_name: dofek-portainer
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portainer is mounted with the Docker socket read-only. That will prevent most management actions (restarts, exec, container updates) which defeats the purpose of Portainer as a management UI. If you intend Portainer to manage the host, mount /var/run/docker.sock read-write (or explicitly document that Portainer is view-only).

Suggested change
- /var/run/docker.sock:/var/run/docker.sock:ro
- /var/run/docker.sock:/var/run/docker.sock

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Removed :ro — Portainer needs read-write access for container management (restart, exec, etc.).

Comment thread .github/workflows/deploy-web.yml Outdated
Comment on lines +49 to +51
# Zero-downtime rollout
docker compose --env-file .env.prod --env-file .env.deploy \
-f docker-compose.deploy.yml rollout web

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-rollout is installed as a docker CLI plugin (docker rollout ...), but this workflow invokes it as docker compose ... rollout web. Unless you’ve added a Compose subcommand extension, this will fail at deploy time. Update the deploy command to use the docker-rollout CLI as intended (and ensure it’s available on the server).

Suggested change
# Zero-downtime rollout
docker compose --env-file .env.prod --env-file .env.deploy \
-f docker-compose.deploy.yml rollout web
# Ensure the docker-rollout CLI plugin is available on the server
docker rollout --help >/dev/null
# Zero-downtime rollout
set -a
. ./.env.prod
. ./.env.deploy
set +a
docker rollout -f docker-compose.deploy.yml web

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Changed to docker rollout -f docker-compose.deploy.yml web — docker-rollout is a Docker CLI plugin, not a compose subcommand. Good catch, this would have failed at deploy time.

Comment thread .github/workflows/deploy-worker.yml Outdated
Comment on lines +50 to +51
docker compose --env-file .env.prod --env-file .env.deploy \
-f docker-compose.deploy.yml rollout worker

Copilot AI Apr 11, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docker-rollout is installed as a docker CLI plugin (docker rollout ...), but this workflow invokes it as docker compose ... rollout worker. Unless you’ve added a Compose subcommand extension, this will fail at deploy time. Update the deploy command to use the docker-rollout CLI as intended (and ensure it’s available on the server).

Suggested change
docker compose --env-file .env.prod --env-file .env.deploy \
-f docker-compose.deploy.yml rollout worker
docker rollout --env-file .env.prod --env-file .env.deploy \
-f docker-compose.deploy.yml worker

Copilot uses AI. Check for mistakes.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 2b35765d. Same fix as web — docker rollout -f docker-compose.deploy.yml worker.

Asherlc and others added 2 commits April 10, 2026 17:08
Resolve conflicts:
- docker-compose.yml: keep our version (no OTA/dokploy-network in dev)
- deploy/dokploy/infra-compose.yml: keep deletion (replaced by deploy compose)
- Pick up OTA image pin to v2.3.16 from main

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix docker-rollout CLI invocation: `docker rollout -f` not `docker compose rollout`
- Pin docker-rollout to v0.13 (it's a shell script, no arch-specific binary)
- Install Infisical via explicit GPG key + apt repo, not pipe-to-bash
- Move GHCR login from cloud-init to deploy workflows (avoids token in logs/TF state)
- Pass Infisical token via SendEnv, not positional arg (avoids process listing exposure)
- Remove :ro from Portainer docker socket mount (needs rw for management)
- Fix dev compose OTel config path to use repo-relative path

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Asherlc

Asherlc commented Apr 11, 2026

Copy link
Copy Markdown
Owner Author

CR Summary

All 7 comments addressed in 2b35765d:

Fixed

# File Issue Fix
1 cloud-init.yml docker-rollout not version-pinned Pinned to v0.13
2 cloud-init.yml Infisical installed via pipe-to-bash Explicit GPG key + apt repo
3 cloud-init.yml GHCR token leaked in cloud-init logs Moved login to deploy workflows
4 secret-sync.yml Infisical token in process listings Use SendEnv instead of positional arg
5 deploy.yml Portainer socket :ro blocks management Removed :ro
6 deploy-web.yml Wrong docker-rollout syntax docker rollout -f (CLI plugin, not compose subcommand)
7 deploy-worker.yml Same syntax issue Same fix

Also fixed the suppressed comment about dev compose OTel config path (/opt/dofek/... → ./deploy/...).

The OTA service moved from docker-compose.yml (dev) to
deploy/docker-compose.deploy.yml (production). Update the test
to read the correct file.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@Asherlc
Asherlc merged commit 1609522 into main Apr 11, 2026
55 checks passed
@Asherlc
Asherlc deleted the Asherlc/explore-dokploy-alt branch April 11, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants