Skip to content

Conversation

@tomo2403
Copy link
Contributor

@tomo2403 tomo2403 commented Feb 21, 2025

This hook allows the user to deploy certificates to multiple services at once. It can store configurations for numerous services, even for the same hook.

Example

You have three Docker containers and a Synology NAS (DSM). However, using the docker and synology_dsm hooks, you can only deploy to one Docker container with renewals. This problem is solved with Multideploy.

Sample config file

The file can be named multideploy.yml or multideploy.yaml. It is stored in the domain folder. $DOMAIN_DIR is a variable that allows deploying certificated to a dir named after the certificate's domain to make changes easier.

version: "1.0"

configs:
  - name: "default"
    services:
      - "webserver"
      - "webserver2"

services:
  - name: "webserver"
    hook: "docker"
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL: "sh.acme.autoload.domain=example.com"
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/key.pem"
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/cert.pem"
      - DEPLOY_DOCKER_CONTAINER_CA_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/ca.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/full.pem"
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD: "service nginx force-reload"
  - name: "webserver2"
    hook: "docker"
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL: "sh.acme.autoload.domain=example.com"
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/key.pem"
      - DEPLOY_DOCKER_CONTAINER_CERT_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/cert.pem"
      - DEPLOY_DOCKER_CONTAINER_CA_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/ca.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE: "/etc/nginx/ssl/$DOMAIN_DIR/full.pem"
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD: "service nginx force-reload"

Wiki

Please tell me if you will merge this first before I start writing a wiki entry for this. thx
https://github.com/acmesh-official/acme.sh/wiki/deployhooks#36-deploying-to-multiple-services-with-the-same-hooks

@gilman88
Copy link

+1 on this kind of capability at least for wildcard certs. I was recently looking at a situation with multiple mikrotik routers where this would have been helpful.

@neilpang
Copy link
Member

please update the wiki page first.

@tomo2403
Copy link
Contributor Author

tomo2403 commented Mar 1, 2025

@neilpang done

@neilpang
Copy link
Member

neilpang commented Mar 2, 2025

let's remove the configs part:

configs:
  - name: "default"
    services:
      - "webserver"
      - "webserver2"

It's not necessary.

In the yaml example, please add some other hooks, not just docker hook. because it should work with any hooks.

Don't use a hardcoded 'multideploy.yml" file, let's make it a env variable, just like the others:

export  DEPLOY_YAML="/path/to/my/multideploy.yaml"
acme.sh --deploy -d xxxx.com  --deploy-hook  multideploy

You can just copy the "$DEPLOY_YAML" file to the domain folder, it will be easier for the user to use.

@tomo2403
Copy link
Contributor Author

tomo2403 commented Mar 2, 2025

The configurations (configs) are intended to simplify the testing/staging process. They allow the user to quickly select or deselect the services the certificate should be deployed to without having to comment out every line. Soon, I also want to implement an overriding functionality similar to Docker (compose.override.yaml). Configurations will then be even more necessary to enhance testing and deployment further when multiple certificates are deployed to the same services. This would allow the user to maintain a minimal, non-redundant configuration. That is also the reason for the hardcoded filepath.

Do you agree with this @neilpang?

@neilpang
Copy link
Member

neilpang commented Mar 4, 2025

The configurations (configs) are intended to simplify the testing/staging process.

no, this is too complicated.

That is also the reason for the hardcoded filepath.

no, use the env variable to pass value. it's the same way as others.

@tomo2403
Copy link
Contributor Author

@neilpang, I removed configs and introduced a variable deploy file name. The wiki is now up to date.

@tomo2403 tomo2403 requested review from lojzik and zeocax April 12, 2025 17:46
Copy link

@zeocax zeocax left a comment

Choose a reason for hiding this comment

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

tested and working fine for me

@tomo2403 tomo2403 requested a review from neilpang April 13, 2025 14:40
@Gerporgl
Copy link

I tested this and I'm looking forward to have this merged.
I needed this to auto renew and publish my wildcard certificate to my synology and nginx servers.
Very useful.

@invario
Copy link
Contributor

invario commented May 27, 2025

@tomo2403 First, thanks so much for putting this together!

I grabbed the file and am attempting to use it within a docker container after I added yq in. My multideploy.yml looks like this (very simple for testing purposes)

# check your acme.sh version in wiki!
version: 1.0

services:
  - name: "npm"
    hook: "docker"
    environment:
      - DEPLOY_DOCKER_CONTAINER_LABEL: "sh.acme.autoload.service=example.com"
      - DEPLOY_DOCKER_CONTAINER_KEY_FILE: "/data/tls/custom/npm-1/privkey.pem"
      - DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE: "/data/tls/custom/npm-1/fullchain.pem"
      - DEPLOY_DOCKER_CONTAINER_RELOAD_CMD: "nginx -s reload"

I then executed the following:
acme.sh --deploy -d example.com --deploy-hook multideploy

But unfortunately, it is returning this:

[Tue May 27 10:30:50 EDT 2025] The domain 'example.com' seems to already have an ECC cert, let's use it.
[Tue May 27 10:30:50 EDT 2025] MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'.
[Tue May 27 10:30:50 EDT 2025] Deploying to 'npm' using 'docker'
[Tue May 27 10:30:50 EDT 2025] The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container.
[Tue May 27 10:30:50 EDT 2025] See: https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers
[Tue May 27 10:30:50 EDT 2025] Container id:
[Tue May 27 10:30:50 EDT 2025] can not find container id
[Tue May 27 10:30:50 EDT 2025] Error deploying for domain: example.com
[Tue May 27 10:30:50 EDT 2025] Error encountered while deploying.
[Tue May 27 10:30:50 EDT 2025] Success

So it seems like the environment variable isn't being set for some reason, very odd.

@tomo2403
Copy link
Contributor Author

tomo2403 commented May 27, 2025

Hello @invario,

So it seems like the environment variable isn't being set for some reason, very odd.

  1. How many times have you tried this?
  2. Could you please share the contents of your example.com.conf file?
  3. Also, please post the output of your command with debug enabled:
acme.sh --deploy -d example.com --deploy-hook multideploy --debug 3

@invario
Copy link
Contributor

invario commented May 27, 2025

Hello @invario,

So it seems like the environment variable isn't being set for some reason, very odd.

  1. How many times have you tried this?

At least 5 times.. Even completely wiped the multideploy.yml file and copy and pasted from your example and manually typed it in just in case there were something off.

  1. Could you please share the contents of your example.com.conf file?
Le_Domain='example.com'
Le_Alt='*.example.com'
Le_Webroot='dns_cf'
Le_PreHook=''
Le_PostHook=''
Le_RenewHook=''
Le_API='https://acme-v02.api.letsencrypt.org/directory'
Le_Keylength='ec-256'
Le_OrderFinalize='https://acme-v02.api.letsencrypt.org/acme/finalize/**BLOCKED**'
CF_Token='**BLOCKED**'
CF_Account_ID=''
CF_Zone_ID='**BLOCKED**'
Le_LinkOrder='https://acme-v02.api.letsencrypt.org/acme/order/2421948647/387838364687'
Le_LinkCert='https://acme-v02.api.letsencrypt.org/acme/cert/065ca48054c135c1fc336ed5367345528824'
Le_CertCreateTime='1748228453'
Le_CertCreateTimeStr='2025-05-26T03:00:53Z'
Le_NextRenewTimeStr='2025-07-24T03:00:53Z'
Le_NextRenewTime='1753326053'
Le_DeployHook='multideploy'

The above is what the example.com.conf looks like prior to running the deploy command. Aftewards, an additional 3 line are added:

DEPLOY_DOCKER_CONTAINER_KEY_FILE='/data/tls/custom/npm-1/privkey.pem'
DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE='/data/tls/custom/npm-1/fullchain.pem'
DEPLOY_DOCKER_CONTAINER_RELOAD_CMD='nginx -s reload'
  1. Also, please post the output of your command with debug enabled:
acme.sh --deploy -d example.com --deploy-hook multideploy --debug 3
Debug log as follows:
[Tue May 27 10:45:33 EDT 2025] Let's find the script directory.
[Tue May 27 10:45:33 EDT 2025] _SCRIPT_='/usr/local/bin/acme.sh'
[Tue May 27 10:45:33 EDT 2025] _script='/root/.acme.sh/acme.sh'
[Tue May 27 10:45:33 EDT 2025] _script_home='/root/.acme.sh'
[Tue May 27 10:45:33 EDT 2025] Using default home: /root/.acme.sh
[Tue May 27 10:45:33 EDT 2025] Using config home: /acme.sh
[Tue May 27 10:45:33 EDT 2025] LE_WORKING_DIR='/root/.acme.sh'
https://github.com/acmesh-official/acme.sh
v3.1.2
[Tue May 27 10:45:33 EDT 2025] Running cmd: deploy
[Tue May 27 10:45:33 EDT 2025] Using config home: /acme.sh
[Tue May 27 10:45:33 EDT 2025] default_acme_server
[Tue May 27 10:45:33 EDT 2025] ACME_DIRECTORY='https://acme.zerossl.com/v2/DV90'
[Tue May 27 10:45:33 EDT 2025] _ACME_SERVER_HOST='acme.zerossl.com'
[Tue May 27 10:45:33 EDT 2025] _ACME_SERVER_PATH='v2/DV90'
[Tue May 27 10:45:33 EDT 2025] The domain 'example.com' seems to already have an ECC cert, let's use it.
[Tue May 27 10:45:33 EDT 2025] DOMAIN_PATH='/acme.sh/example.com_ecc'
[Tue May 27 10:45:33 EDT 2025] DOMAIN_CONF='/acme.sh/example.com_ecc/example.com.conf'
[Tue May 27 10:45:33 EDT 2025] _deployApi='/root/.acme.sh/deploy/multideploy.sh'
[Tue May 27 10:45:33 EDT 2025] _cdomain='example.com'
[Tue May 27 10:45:33 EDT 2025] _ckey='/acme.sh/example.com_ecc/example.com.key'
[Tue May 27 10:45:33 EDT 2025] _ccert='/acme.sh/example.com_ecc/example.com.cer'
[Tue May 27 10:45:33 EDT 2025] _cca='/acme.sh/example.com_ecc/ca.cer'
[Tue May 27 10:45:33 EDT 2025] _cfullchain='/acme.sh/cexample.com_ecc/fullchain.cer'
[Tue May 27 10:45:33 EDT 2025] _cpfx='/acme.sh/example.com_ecc/example.com.pfx'
[Tue May 27 10:45:33 EDT 2025] DOMAIN_DIR='example.com_ecc'
[Tue May 27 10:45:33 EDT 2025] MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'.
[Tue May 27 10:45:33 EDT 2025] Deploy file='/acme.sh/example.com_ecc/multideploy.yml'
[Tue May 27 10:45:33 EDT 2025] Deploy file version is compatible: 1.0
[Tue May 27 10:45:33 EDT 2025] Services='npm'
[Tue May 27 10:45:33 EDT 2025] Config has services.
[Tue May 27 10:45:33 EDT 2025] Checking service='npm'
[Tue May 27 10:45:33 EDT 2025] Service='npm'
[Tue May 27 10:45:33 EDT 2025] SERVICE='npm'
[Tue May 27 10:45:34 EDT 2025] HOOK='docker'
[Tue May 27 10:45:34 EDT 2025] Deploying to 'npm' using 'docker'
[Tue May 27 10:45:34 EDT 2025] _deployApi='/root/.acme.sh/deploy/docker.sh'
[Tue May 27 10:45:34 EDT 2025] _cdomain='example.com'
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_LABEL
[Tue May 27 10:45:34 EDT 2025] The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container.
[Tue May 27 10:45:34 EDT 2025] See: https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers
[Tue May 27 10:45:34 EDT 2025] Try use /var/run/docker.sock
[Tue May 27 10:45:34 EDT 2025] _cversion='8.12.1'
[Tue May 27 10:45:34 EDT 2025] _major='8'
[Tue May 27 10:45:34 EDT 2025] _minor='12'
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_KEY_FILE
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_CERT_FILE
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_CA_FILE
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_PFX_FILE
[Tue May 27 10:45:34 EDT 2025] DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
[Tue May 27 10:45:34 EDT 2025] _req='{"label":[""]}'
[Tue May 27 10:45:34 EDT 2025] _req='%7b%22label%22%3a%5b%22%22%5d%7d'
[Tue May 27 10:45:34 EDT 2025] _data
[Tue May 27 10:45:34 EDT 2025] url='http://localhost/containers/json?filters=%7b%22label%22%3a%5b%22%22%5d%7d'
10:45:34.067005 [0-x] == Info: [READ] client_reset, clear readers
10:45:34.067143 [0-0] == Info: [SETUP] added
10:45:34.067222 [0-0] == Info:   Trying /var/run/docker.sock:0...
10:45:34.067332 [0-0] == Info: Connected to localhost (/var/run/docker.sock) port 0
10:45:34.067438 [0-0] == Info: using HTTP/1.x
10:45:34.067505 [0-0] => Send header, 180 bytes (0xb4)
0000: GET /containers/json?filters=%7b%22label%22%3a%5b%22%22%5d%7d HT
0040: TP/1.1
0048: Host: localhost
0059: User-Agent: curl/8.12.1
0072: Accept: */*
007f: Content-Type: application/json
009f: Content-Length: 0
00b2:
10:45:34.067904 [0-0] == Info: Request completely sent off
10:45:34.068005 [0-0] <= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
10:45:34.068115 [0-0] == Info: [WRITE] cw_out, wrote 17 header bytes -> 17
10:45:34.068227 [0-0] == Info: [WRITE] download_write header(type=c, blen=17) -> 0
10:45:34.068322 [0-0] == Info: [WRITE] client_write(type=c, len=17) -> 0
10:45:34.068409 [0-0] <= Recv header, 19 bytes (0x13)
0000: Api-Version: 1.47
10:45:34.068520 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=19) -> 0
10:45:34.068617 [0-0] == Info: [WRITE] cw_out, wrote 19 header bytes -> 19
10:45:34.068724 [0-0] == Info: [WRITE] download_write header(type=4, blen=19) -> 0
10:45:34.068828 [0-0] == Info: [WRITE] client_write(type=4, len=19) -> 0
10:45:34.068917 [0-0] <= Recv header, 32 bytes (0x20)
0000: Content-Type: application/json
10:45:34.069036 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=32) -> 0
10:45:34.069134 [0-0] == Info: [WRITE] cw_out, wrote 32 header bytes -> 32
10:45:34.069233 [0-0] == Info: [WRITE] download_write header(type=4, blen=32) -> 0
10:45:34.069347 [0-0] == Info: [WRITE] client_write(type=4, len=32) -> 0
10:45:34.069443 [0-0] <= Recv header, 28 bytes (0x1c)
0000: Docker-Experimental: false
10:45:34.069585 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=28) -> 0
10:45:34.069658 [0-0] == Info: [WRITE] cw_out, wrote 28 header bytes -> 28
10:45:34.069755 [0-0] == Info: [WRITE] download_write header(type=4, blen=28) -> 0
10:45:34.069874 [0-0] == Info: [WRITE] client_write(type=4, len=28) -> 0
10:45:34.069973 [0-0] <= Recv header, 15 bytes (0xf)
0000: Ostype: linux
10:45:34.070058 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=15) -> 0
10:45:34.070160 [0-0] == Info: [WRITE] cw_out, wrote 15 header bytes -> 15
10:45:34.070253 [0-0] == Info: [WRITE] download_write header(type=4, blen=15) -> 0
10:45:34.070352 [0-0] == Info: [WRITE] client_write(type=4, len=15) -> 0
10:45:34.070457 [0-0] <= Recv header, 31 bytes (0x1f)
0000: Server: Docker/27.5.0 (linux)
10:45:34.070567 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=31) -> 0
10:45:34.070684 [0-0] == Info: [WRITE] cw_out, wrote 31 header bytes -> 31
10:45:34.070778 [0-0] == Info: [WRITE] download_write header(type=4, blen=31) -> 0
10:45:34.070873 [0-0] == Info: [WRITE] client_write(type=4, len=31) -> 0
10:45:34.070968 [0-0] <= Recv header, 37 bytes (0x25)
0000: Date: Tue, 27 May 2025 14:45:34 GMT
10:45:34.071078 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=37) -> 0
10:45:34.071182 [0-0] == Info: [WRITE] cw_out, wrote 37 header bytes -> 37
10:45:34.071275 [0-0] == Info: [WRITE] download_write header(type=4, blen=37) -> 0
10:45:34.071369 [0-0] == Info: [WRITE] client_write(type=4, len=37) -> 0
10:45:34.071462 [0-0] <= Recv header, 19 bytes (0x13)
0000: Content-Length: 3
10:45:34.071566 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=19) -> 0
10:45:34.071666 [0-0] == Info: [WRITE] cw_out, wrote 19 header bytes -> 19
10:45:34.071757 [0-0] == Info: [WRITE] download_write header(type=4, blen=19) -> 0
10:45:34.071858 [0-0] == Info: [WRITE] client_write(type=4, len=19) -> 0
10:45:34.071940 [0-0] <= Recv header, 2 bytes (0x2)
0000:
10:45:34.072005 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=2) -> 0
10:45:34.072094 [0-0] == Info: [WRITE] cw_out, wrote 2 header bytes -> 2
10:45:34.072173 [0-0] == Info: [WRITE] download_write header(type=4, blen=2) -> 0
10:45:34.072262 [0-0] == Info: [WRITE] client_write(type=4, len=2) -> 0
10:45:34.072342 [0-0] <= Recv data, 3 bytes (0x3)
0000: [].
10:45:34.072442 [0-0] == Info: [WRITE] cw_out, wrote 3 body bytes -> 3
10:45:34.072517 [0-0] == Info: [WRITE] download_write body(type=1, blen=3) -> 0
10:45:34.072627 [0-0] == Info: [WRITE] client_write(type=1, len=3) -> 0
10:45:34.072709 [0-0] == Info: [WRITE] xfer_write_resp(len=203, eos=0) -> 0
10:45:34.072809 [0-0] == Info: [WRITE] cw-out is notpaused
10:45:34.072887 [0-0] == Info: [WRITE] cw-out done
10:45:34.072942 [0-0] == Info: [READ] client_reset, clear readers
10:45:34.073040 [0-0] == Info: Connection #0 to host localhost left intact
[Tue May 27 10:45:34 EDT 2025] listjson='[]'
[Tue May 27 10:45:34 EDT 2025] Container id:
[Tue May 27 10:45:34 EDT 2025] can not find container id
[Tue May 27 10:45:34 EDT 2025] Error deploying for domain: example.com
[Tue May 27 10:45:34 EDT 2025] Error encountered while deploying.
[Tue May 27 10:45:34 EDT 2025] Setting Le_DeployHook
[Tue May 27 10:45:34 EDT 2025] Success

@Gerporgl
Copy link

Gerporgl commented May 27, 2025

Maybe I can also add something weird I have observed and that I did not mention in my previous comment.

I am also getting errors, but not all the time. I am using SSH deploy and Synology deploy.

Sometimes the SSH deploy is giving me "DEPLOY_SSH_USER not defined." then "Error deploying for domain: ..." and then "Error encountered while deploying.", but then proceed successfully to the next deploy (Synology).
When I retry, it usually works. In fact, it seems to be really 50/50% work/fail. And I mean now that I am testing this more, it works once, and seems to be guaranteed to not work next time.
I am obviously worried that my next automated renewal will not work.

I also see this line most of the time, not sure if this is an error: "./acme.sh: 241: [: unexpected operator".

I've assumed there was still some race condition issues that hopefully would get resolved at some point...

Perhaps this is a different problem.

@invario
Copy link
Contributor

invario commented May 27, 2025

Hang on, here's a new debug log with `--output-insecure` included which might be more helpful:
[Tue May 27 11:42:03 EDT 2025] readlink exists=0
[Tue May 27 11:42:03 EDT 2025] dirname exists=0
[Tue May 27 11:42:03 EDT 2025] Let's find the script directory.
[Tue May 27 11:42:03 EDT 2025] _SCRIPT_='/usr/local/bin/acme.sh'
[Tue May 27 11:42:03 EDT 2025] _script='/root/.acme.sh/acme.sh'
[Tue May 27 11:42:03 EDT 2025] _script_home='/root/.acme.sh'
[Tue May 27 11:42:03 EDT 2025] Using default home: /root/.acme.sh
[Tue May 27 11:42:03 EDT 2025] Using config home: /acme.sh
[Tue May 27 11:42:03 EDT 2025] ACCOUNT_CONF_PATH='/acme.sh/account.conf'
[Tue May 27 11:42:03 EDT 2025] OK
[Tue May 27 11:42:03 EDT 2025] 1:AUTO_UPGRADE='1'
[Tue May 27 11:42:03 EDT 2025] LE_WORKING_DIR='/root/.acme.sh'
https://github.com/acmesh-official/acme.sh
v3.1.2
[Tue May 27 11:42:03 EDT 2025] Running cmd: deploy
[Tue May 27 11:42:03 EDT 2025] Using config home: /acme.sh
[Tue May 27 11:42:03 EDT 2025] ACCOUNT_CONF_PATH='/acme.sh/account.conf'
[Tue May 27 11:42:03 EDT 2025] default_acme_server
[Tue May 27 11:42:03 EDT 2025] ACME_DIRECTORY='https://acme.zerossl.com/v2/DV90'
[Tue May 27 11:42:03 EDT 2025] _ACME_SERVER_HOST='acme.zerossl.com'
[Tue May 27 11:42:03 EDT 2025] _ACME_SERVER_PATH='v2/DV90'
[Tue May 27 11:42:03 EDT 2025] CA_CONF='/acme.sh/ca/acme.zerossl.com/v2/DV90/ca.conf'
[Tue May 27 11:42:03 EDT 2025] The domain 'example.com' seems to already have an ECC cert, let's use it.
[Tue May 27 11:42:03 EDT 2025] DOMAIN_PATH='/acme.sh/example.com_ecc'
[Tue May 27 11:42:03 EDT 2025] DOMAIN_CONF='/acme.sh/example.com_ecc/example.com.conf'
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 19:Le_DeployHook='multideploy,'
[Tue May 27 11:42:03 EDT 2025] _deployApi='/root/.acme.sh/deploy/multideploy.sh'
[Tue May 27 11:42:03 EDT 2025] multideploy_deploy exists=0
[Tue May 27 11:42:03 EDT 2025] _cdomain='example.com'
[Tue May 27 11:42:03 EDT 2025] _ckey='/acme.sh/example.com_ecc/example.com.key'
[Tue May 27 11:42:03 EDT 2025] _ccert='/acme.sh/example.com_ecc/example.com.cer'
[Tue May 27 11:42:03 EDT 2025] _cca='/acme.sh/example.com_ecc/ca.cer'
[Tue May 27 11:42:03 EDT 2025] _cfullchain='/acme.sh/example.com_ecc/fullchain.cer'
[Tue May 27 11:42:03 EDT 2025] _cpfx='/acme.sh/example.com_ecc/example.com.pfx'
[Tue May 27 11:42:03 EDT 2025] DOMAIN_DIR='example.com_ecc'
[Tue May 27 11:42:03 EDT 2025] MULTIDEPLOY_FILENAME is not set, so I will use 'multideploy.yml'.
[Tue May 27 11:42:03 EDT 2025] yq is installed.
[Tue May 27 11:42:03 EDT 2025] Checking file='/acme.sh/example.com_ecc/multideploy.yml'
[Tue May 27 11:42:03 EDT 2025] File found
[Tue May 27 11:42:03 EDT 2025] Deploy file='/acme.sh/example.com_ecc/multideploy.yml'
[Tue May 27 11:42:03 EDT 2025] Deploy file version is compatible: 1.0
[Tue May 27 11:42:03 EDT 2025] Services='npm'
[Tue May 27 11:42:03 EDT 2025] Config has services.
[Tue May 27 11:42:03 EDT 2025] Checking service='npm'
[Tue May 27 11:42:03 EDT 2025] File='/acme.sh/example.com_ecc/multideploy.yml'
[Tue May 27 11:42:03 EDT 2025] Deploy file='/acme.sh/example.com_ecc/multideploy.yml'
[Tue May 27 11:42:03 EDT 2025] Services='npm'
[Tue May 27 11:42:03 EDT 2025] Service='npm'
[Tue May 27 11:42:03 EDT 2025] Exporting envs='DEPLOY_DOCKER_CONTAINER_LABEL: "sh.acme.autoload.service=example.com"
DEPLOY_DOCKER_CONTAINER_KEY_FILE: "/data/tls/custom/npm-1/privkey.pem"
DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE: "/data/tls/custom/npm-1/fullchain.pem"
DEPLOY_DOCKER_CONTAINER_RELOAD_CMD: "nginx -s reload"'
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 20:DEPLOY_DOCKER_CONTAINER_LABEL='sh.acme.autoload.service=example.com'
[Tue May 27 11:42:03 EDT 2025] Saved DEPLOY_DOCKER_CONTAINER_LABEL='sh.acme.autoload.service=example.com'
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 21:DEPLOY_DOCKER_CONTAINER_KEY_FILE='/data/tls/custom/npm-1/privkey.pem'
[Tue May 27 11:42:03 EDT 2025] Saved DEPLOY_DOCKER_CONTAINER_KEY_FILE='/data/tls/custom/npm-1/privkey.pem'
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 22:DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE='/data/tls/custom/npm-1/fullchain.pem'
[Tue May 27 11:42:03 EDT 2025] Saved DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE='/data/tls/custom/npm-1/fullchain.pem'
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 23:DEPLOY_DOCKER_CONTAINER_RELOAD_CMD='nginx -s reload'
[Tue May 27 11:42:03 EDT 2025] Saved DEPLOY_DOCKER_CONTAINER_RELOAD_CMD='nginx -s reload'
[Tue May 27 11:42:03 EDT 2025] SERVICE='npm'
[Tue May 27 11:42:03 EDT 2025] HOOK='docker'
[Tue May 27 11:42:03 EDT 2025] Deploying to 'npm' using 'docker'
[Tue May 27 11:42:03 EDT 2025] _deployApi='/root/.acme.sh/deploy/docker.sh'
[Tue May 27 11:42:03 EDT 2025] docker_deploy exists=0
[Tue May 27 11:42:03 EDT 2025] _cdomain='example.com'
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_LABEL
[Tue May 27 11:42:03 EDT 2025] The DEPLOY_DOCKER_CONTAINER_LABEL variable is not defined, we use this label to find the container.
[Tue May 27 11:42:03 EDT 2025] See: https://github.com/acmesh-official/acme.sh/wiki/deploy-to-docker-containers
[Tue May 27 11:42:03 EDT 2025] APP
[Tue May 27 11:42:03 EDT 2025] 24:SAVED_DEPLOY_DOCKER_CONTAINER_LABEL=''
[Tue May 27 11:42:03 EDT 2025] docker exists=127
[Tue May 27 11:42:03 EDT 2025] Try use /var/run/docker.sock
[Tue May 27 11:42:03 EDT 2025] curl exists=0
[Tue May 27 11:42:03 EDT 2025] _cversion='8.12.1'
[Tue May 27 11:42:03 EDT 2025] _major='8'
[Tue May 27 11:42:03 EDT 2025] _minor='12'
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_KEY_FILE
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_CERT_FILE
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_CA_FILE
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_PFX_FILE
[Tue May 27 11:42:03 EDT 2025] DEPLOY_DOCKER_CONTAINER_RELOAD_CMD
[Tue May 27 11:42:03 EDT 2025] _req='{"label":[""]}'
[Tue May 27 11:42:03 EDT 2025] od exists=0
[Tue May 27 11:42:03 EDT 2025] _url_encode
[Tue May 27 11:42:03 EDT 2025] _hex_str=' 7b 22 6c 61 62 65 6c 22 3a 5b 22 22 5d 7d'
[Tue May 27 11:42:03 EDT 2025] _req='%7b%22label%22%3a%5b%22%22%5d%7d'
[Tue May 27 11:42:03 EDT 2025] _data
[Tue May 27 11:42:03 EDT 2025] url='http://localhost/containers/json?filters=%7b%22label%22%3a%5b%22%22%5d%7d'
11:42:03.848065 [0-x] == Info: [READ] client_reset, clear readers
11:42:03.848336 [0-0] == Info: [SETUP] added
11:42:03.848407 [0-0] == Info:   Trying /var/run/docker.sock:0...
11:42:03.848507 [0-0] == Info: Connected to localhost (/var/run/docker.sock) port 0
11:42:03.848615 [0-0] == Info: using HTTP/1.x
11:42:03.848693 [0-0] => Send header, 180 bytes (0xb4)
0000: GET /containers/json?filters=%7b%22label%22%3a%5b%22%22%5d%7d HT
0040: TP/1.1
0048: Host: localhost
0059: User-Agent: curl/8.12.1
0072: Accept: */*
007f: Content-Type: application/json
009f: Content-Length: 0
00b2:
11:42:03.849070 [0-0] <= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
11:42:03.849176 [0-0] == Info: [WRITE] cw_out, wrote 17 header bytes -> 17
11:42:03.849264 [0-0] == Info: [WRITE] download_write header(type=c, blen=17) -> 0
11:42:03.849358 [0-0] == Info: [WRITE] client_write(type=c, len=17) -> 0
11:42:03.849444 [0-0] <= Recv header, 19 bytes (0x13)
0000: Api-Version: 1.47
11:42:03.849652 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=19) -> 0
11:42:03.849758 [0-0] == Info: [WRITE] cw_out, wrote 19 header bytes -> 19
11:42:03.849826 [0-0] == Info: [WRITE] download_write header(type=4, blen=19) -> 0
11:42:03.849901 [0-0] == Info: [WRITE] client_write(type=4, len=19) -> 0
11:42:03.849969 [0-0] <= Recv header, 32 bytes (0x20)
0000: Content-Type: application/json
11:42:03.850053 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=32) -> 0
11:42:03.850127 [0-0] == Info: [WRITE] cw_out, wrote 32 header bytes -> 32
11:42:03.850195 [0-0] == Info: [WRITE] download_write header(type=4, blen=32) -> 0
11:42:03.850270 [0-0] == Info: [WRITE] client_write(type=4, len=32) -> 0
11:42:03.850336 [0-0] <= Recv header, 28 bytes (0x1c)
0000: Docker-Experimental: false
11:42:03.850416 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=28) -> 0
11:42:03.850490 [0-0] == Info: [WRITE] cw_out, wrote 28 header bytes -> 28
11:42:03.850687 [0-0] == Info: [WRITE] download_write header(type=4, blen=28) -> 0
11:42:03.850851 [0-0] == Info: [WRITE] client_write(type=4, len=28) -> 0
11:42:03.850949 [0-0] <= Recv header, 15 bytes (0xf)
0000: Ostype: linux
11:42:03.851018 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=15) -> 0
11:42:03.851093 [0-0] == Info: [WRITE] cw_out, wrote 15 header bytes -> 15
11:42:03.851181 [0-0] == Info: [WRITE] download_write header(type=4, blen=15) -> 0
11:42:03.851255 [0-0] == Info: [WRITE] client_write(type=4, len=15) -> 0
11:42:03.851328 [0-0] <= Recv header, 31 bytes (0x1f)
0000: Server: Docker/27.5.0 (linux)
11:42:03.851416 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=31) -> 0
11:42:03.851527 [0-0] == Info: [WRITE] cw_out, wrote 31 header bytes -> 31
11:42:03.851640 [0-0] == Info: [WRITE] download_write header(type=4, blen=31) -> 0
11:42:03.851757 [0-0] == Info: [WRITE] client_write(type=4, len=31) -> 0
11:42:03.851849 [0-0] <= Recv header, 37 bytes (0x25)
0000: Date: Tue, 27 May 2025 15:42:03 GMT
11:42:03.851995 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=37) -> 0
11:42:03.852115 [0-0] == Info: [WRITE] cw_out, wrote 37 header bytes -> 37
11:42:03.852223 [0-0] == Info: [WRITE] download_write header(type=4, blen=37) -> 0
11:42:03.852319 [0-0] == Info: [WRITE] client_write(type=4, len=37) -> 0
11:42:03.852424 [0-0] <= Recv header, 19 bytes (0x13)
0000: Content-Length: 3
11:42:03.852544 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=19) -> 0
11:42:03.852655 [0-0] == Info: [WRITE] cw_out, wrote 19 header bytes -> 19
11:42:03.852772 [0-0] == Info: [WRITE] download_write header(type=4, blen=19) -> 0
11:42:03.852878 [0-0] == Info: [WRITE] client_write(type=4, len=19) -> 0
11:42:03.852976 [0-0] <= Recv header, 2 bytes (0x2)
0000:
11:42:03.853055 [0-0] == Info: [WRITE] header_collect pushed(type=1, len=2) -> 0
11:42:03.853163 [0-0] == Info: [WRITE] cw_out, wrote 2 header bytes -> 2
11:42:03.853250 [0-0] == Info: [WRITE] download_write header(type=4, blen=2) -> 0
11:42:03.853358 [0-0] == Info: [WRITE] client_write(type=4, len=2) -> 0
11:42:03.853455 [0-0] <= Recv data, 3 bytes (0x3)
0000: [].
11:42:03.853650 [0-0] == Info: [WRITE] cw_out, wrote 3 body bytes -> 3
11:42:03.853739 [0-0] == Info: [WRITE] download_write body(type=1, blen=3) -> 0
11:42:03.853840 [0-0] == Info: [WRITE] client_write(type=1, len=3) -> 0
11:42:03.853935 [0-0] == Info: [WRITE] xfer_write_resp(len=203, eos=0) -> 0
11:42:03.854046 [0-0] == Info: [WRITE] cw-out is notpaused
11:42:03.854135 [0-0] == Info: [WRITE] cw-out done
11:42:03.854202 [0-0] == Info: [READ] client_reset, clear readers
11:42:03.854280 [0-0] == Info: Connection #0 to host localhost left intact
[Tue May 27 11:42:03 EDT 2025] listjson='[]'
[Tue May 27 11:42:03 EDT 2025] Container id:
[Tue May 27 11:42:03 EDT 2025] can not find container id
[Tue May 27 11:42:03 EDT 2025] Error deploying for domain: example.com
[Tue May 27 11:42:03 EDT 2025] Error encountered while deploying.
[Tue May 27 11:42:03 EDT 2025] Clearing envs='DEPLOY_DOCKER_CONTAINER_LABEL: "sh.acme.autoload.service=example.com"
DEPLOY_DOCKER_CONTAINER_KEY_FILE: "/data/tls/custom/npm-1/privkey.pem"
DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE: "/data/tls/custom/npm-1/fullchain.pem"
DEPLOY_DOCKER_CONTAINER_RELOAD_CMD: "nginx -s reload"'
[Tue May 27 11:42:03 EDT 2025] Deleting key='DEPLOY_DOCKER_CONTAINER_LABEL'
[Tue May 27 11:42:03 EDT 2025] Deleting key='DEPLOY_DOCKER_CONTAINER_KEY_FILE'
[Tue May 27 11:42:03 EDT 2025] Deleting key='DEPLOY_DOCKER_CONTAINER_FULLCHAIN_FILE'
[Tue May 27 11:42:03 EDT 2025] Deleting key='DEPLOY_DOCKER_CONTAINER_RELOAD_CMD'
[Tue May 27 11:42:03 EDT 2025] Setting Le_DeployHook
[Tue May 27 11:42:03 EDT 2025] OK
[Tue May 27 11:42:03 EDT 2025] 19:Le_DeployHook='multideploy'
[Tue May 27 11:42:03 EDT 2025] Success

Which looks like your deploy script is reading the DEPLOY_DOCKER_CONTAINER_LABEL .

HMM....

@tomo2403
Copy link
Contributor Author

@egarzadev

When testing this out myself I had strange issues with environment variables not being applied consistently. I found that reverting this commit in my local multideploy.sh file seemed to resolve the issues.

Interesting, this commit should not be related to the problem with the environment variables.

@tomo2403
Copy link
Contributor Author

@Gerporgl @invario @egarzadev Could you please pull the latest change and test it again? I can't reproduce this error with my setup.

@invario
Copy link
Contributor

invario commented May 27, 2025

@Gerporgl @invario @egarzadev Could you please pull the latest change and test it again? I can't reproduce this error with my setup.

Pulled, working on my end. Thanks for the quick turnaround.

@invario
Copy link
Contributor

invario commented May 28, 2025

I'd like to make a suggestion that if a service fails to deploy properly, a non-zero return code should be returned upon completing deployment of all other services. Perhaps even something simple like increment the return code by 1 for each service that fails.

@Q3792274
Copy link

dhparams

tomo2403 and others added 27 commits December 27, 2025 11:02
- Prefer using a pipe to `while read`
- But use a temp file when the loop needs to modify variables that need to be used outside the loop, as the pipe creates a subshell and modifications do not survive after the loop exits.
The config file checks were returning okay even when there were errors. The yq tool returns "null" when it cannot find what's queried, but exists with a 0 rc still.
Before this, checker issues were only logged. This stops the deployment if any configuration is incorrect.
acme.sh takes care of that
Because it causes a mysterious crash and it's honestly not worth it.
Before this, the eval call would try to run some commands (if they were compound commands) in the yaml file on the machine running acme.sh

Eval might not be worth it for the little benefit it brings.
Before this, some deployment scripts would interact with STDIN and that would cause this loop to skip some elements. By using descriptor 3 we avoid clashing with the very common stdin and stdout.
kislyuk yq (used by Debian packages) does not accept `yq e` and
also returns strings with double quotes.

mikefarah's yq-go (used by Alpine) accepts `yq e` and `yq`.

replace `yq e` with `yq` and also use `-r` switch to remove double
quoting to ensure uniform return values from both yq versions.

Signed-off-by: invario <[email protected]>
@neilpang neilpang merged commit 260df00 into acmesh-official:dev Dec 27, 2025
2 checks passed
@neilpang
Copy link
Member

please create a new issue for bug reporting for this hook.

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.