-
Notifications
You must be signed in to change notification settings - Fork 247
Separate publishing to container registries with new azd publish
command
#5663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
ac3fc44
to
917e130
Compare
Hello, I don't know if it can be included in this PR or not but It could be great to be able to publish to another docker registry than ACR. Thanks for your work |
Thank you for the input! I'm testing this scenario out and it looks like it shouldn't be too hard to support in this PR given that azd deploy supports external registries already today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A couple questions and issues to consider.
-
The
ServiceTarget
interface exposesPackage
andDeploy
functions. Given this change we should consider addingPublish
to this interface and implementing it on supported service targets like container app -
Given a new top level command we should also consider exposing new
publish
project & service hooks so users can more easily integrate with this new capability. -
Given the separation of publishing from deploy we should consider whether our default
up
workflow should still only calldeploy
or also includepublish
as well. -
Consider adding
Publish
support for AKS service target (future PR)
c79efb0
to
690bc1f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
690bc1f
to
4c53c10
Compare
azd deploy
azd publish
command
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see how the current implementation is sitting nicely.
I'm pondering about and happy to discuss with you and the rest of the team some overall design thoughts in the comments that I left.
ctx context.Context, | ||
serviceConfig *ServiceConfig, | ||
localImageTag string, | ||
options *PublishOptions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this feel unnatural? I was a little surprised to see RemoteTag()
take in PublishOptions
.
One observation I can offer: docker supports both local and remote contexts in the image tag.
I wonder if there's a line of thought we could apply here: What if packagePath
had local vs. remote segments in the path itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if packagePath had local vs. remote segments in the path itself?
docker supports both local and remote contexts in the image tag.
Could you provide an example?
Are you suggesting ServicePackageResult.PackagePath be something like:
type PackagePath struct {
Local string
Remote string
}
and have RemoteImageTag
use that instead of generating the remote image tag from localImageTag
and PublishOptions
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to continue the discussion for the sake of idealization and education. I'm still working through a set of refactorings I could imagine taking place that can help shape these longer term payoff ideas.
In the most simplistic idealistic world, I would start with a URI.
The frontend (whether it goes through publishing, or via a remote image
reference) would just produce a PackageService
with Path
: example.com:5000/team/my-app:2.0
OR myimage
OR imageId
.
The backend is agnostic to how this was done, the concept of "publishing options" wouldn't materialize at this layer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do already have some prior art here around remote vs local tag in the container helper. Looks like we're reusing that here with docker.ContainerImage
struct.
ctx context.Context, | ||
serviceConfig *ServiceConfig, | ||
packageResult *ServicePackageResult, | ||
publishResult *ServicePublishResult, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally a little nervous to consider "publish" a lifecycle with its own set of components, and having stack another layer here.
I could imagine a natural evolution here is to create proper data structure around PackagedService
which perhaps looks like *ServicePackageResult
, but either just by the path around, or some attribute, indicating whether it's remote. An interesting lead for me is to consider how docker image tags are already shaped today, in this other comment here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are really just teasing out work from deploy since it likely does too much today. I like the idea of have smaller incremental lifecycle events like build, package, publish, deploy that each have single responsibility per host/service target. It will also enable developers to provide more precise integration points from hooks/extensions to do what they want to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rephrasing question for clarity: Should Deploy()
take in both ServicePackageResult
and ServicePublishResult
? What does it mean to have both set?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall this looks amazing! I only have a couple open questions around the following:
- Default overrwrite behavior? Should be true/false if the image/tag already exists?
- Just need to ensure all our e2e tests cases still work to ensure we don't have any regressions.
Flags | ||
--all : Deploys all services that are listed in azure.yaml | ||
-e, --environment string : The name of the environment to use. | ||
--force-publish : Forcibly publishes service, overwriting any existing published artifacts. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused by this flag. What is the use case. Prior to this azd deploy
always publishes a new container to the registry. So is this the same behavior with a default value of true
? Or am I missing a new scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah pretty much, say the user has a hardcoded image and tag in azure.yaml
, which already exists in the remote container registry.
services:
svc:
...
docker:
image: 'web'
tag: latest
If the user runs the following sequence of commands, they'd need to set --force-publish
in order to update the current image in the registry with the newly built one:
azd package web
- producesweb:latest
azd deploy web --from-package web:latest --force-publish
Or if using publish
:
azd package web
- producesweb:latest
azd publish web --from-package web:latest
azd deploy web --from-package web:latest
If we instead inverted the overwrite behaviour (deploy
always publishes), then it might look like this instead:
azd package web
- producesweb:latest
azd deploy web --from-package web:latest
Or if using publish
:
azd package web
- producesweb:latest
azd publish web --from-package web:latest
azd deploy web --from-package web:latest --skip-publish
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I needed to vote:
web:latest
is antipattern, see https://learn.microsoft.com/en-us/azure/container-registry/container-registry-image-tag-version#unique-tags. This leans me to not introduce this behavior or flag until we need it later.- If we needed to address this as a user feature, do the smart thing -- not a configuration, not a flag. Is there a way to do a delta diff and publish the "most latest" image?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From this week's earlier discussion, we agreed to change this so that: if the user runs azd deploy web --from-package
with a fully qualified image reference like myacr.azurecr.io/web:20250918
, that would instruct azd deploy
to skip publishing (and packaging as well). That way we don't need to support any additional --skip-publish
or --force-publish
deploy flags.
Another suggestion that came up was for azd publish
to have a single flag to be able to set the destination registry, image, and tag instead of separate --image
and --tag
. Something like:
# Override registry, repository, and tag
azd publish web --from-package web:20250918--to myacr.azurecr.io/aca/web:latest
# Override repository and tag
azd publish web --from-package web:20250918--to web:latest
# Override registry and repository
azd publish web --from-package web:20250918--to myacr.azurecr.io/aca/web
However, it's a bit unclear how this would support overriding only the tag, or other combinations that separate --tag
and --image
supports.
Any thoughts?
cli/azd/internal/cmd/publish.go
Outdated
"Publish the service named 'api' with image name 'app/api' and tag 'prod'.": output.WithHighLightFormat( | ||
"azd publish api --image app/api --tag prod", | ||
), | ||
"Publish the service named 'api' from a previously generated package.": output.WithHighLightFormat( | ||
"azd publish api --from-package <image-tag>", | ||
), | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we can leverage this new azd publish
behavior to simplify some container based scenarios where it is now closer to gitops type flow where we can just publish a new "latest" tag for the same image and then the host/service target is responsible for the heavy lifting of fetching the updated container, restarting the instance... Only need a full deploy flow if your spec changes or has additional configuration/dependencies.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is interesting. Do you happen to have working scenarios to share?
At a glance, I suspect this may not be easy to do -- the secrets (for example, keyvault secrets) aren't stamped directly into the image.
ctx context.Context, | ||
serviceConfig *ServiceConfig, | ||
localImageTag string, | ||
options *PublishOptions, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We do already have some prior art here around remote vs local tag in the container helper. Looks like we're reusing that here with docker.ContainerImage
struct.
ctx context.Context, | ||
serviceConfig *ServiceConfig, | ||
packageResult *ServicePackageResult, | ||
publishResult *ServicePublishResult, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are really just teasing out work from deploy since it likely does too much today. I like the idea of have smaller incremental lifecycle events like build, package, publish, deploy that each have single responsibility per host/service target. It will also enable developers to provide more precise integration points from hooks/extensions to do what they want to do.
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash:
pwsh:
WindowsPowerShell install
MSI install
Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
Closes #5601
This PR introduces a separate
azd publish
command for publishing container images to a container registry. There are also some minor changes toazd deploy
.TODO
azd publish
azd publish
supports these flags:--all
(similar toazd deploy
)--from-package
(similar toazd deploy
)--image
- override default container image namedocker.image
service property in azure.yaml:--tag
- override default tag nametag
property in azure.yaml:Other changes/notes:
The default
azd up
workflow is unchanged and will still callprovision
anddeploy
, butazd up
can be customized in azure.yaml withazd publish
and the same image reference would be used throughout using theserviceManager
operation result cache--from-package
is compatible with--image
and--tag
. So running the following command would publish the local imagetodo-nodejs-mongo-aca-web:azd-deploy-1757350123
to<acr>.azurecr.io/web-override:override
:azd publish
supports publishing to third-party container registries (likedocker.io
) using thedocker.registry
service property in azure.yaml.Only services with host type
containerapp
andaks
are currently supported. The command succeeds with a warning on unsupported host types:Supports local and remote build
Supports
prepublish
andpostpublish
project and service hooks:The
IMAGE_NAME
service property is still set in the environment for backwards compatibility, but becausepublish
is now a top level action, thedeploy
logic can get the published image fromServicePublishResult
instead of reading from theIMAGE_NAME
environment variable.azd deploy
azd deploy
now by default skips publishing the image to the container registry if the image exists remotely. There's a new flag--force-publish
that forces azd to always publish the image duringdeploy
. If the image does not exist remotely,deploy
still publishes to the registry like previously.Note
Is this behavioural change okay, or should we keep the old behaviour, where
azd deploy
always publishes and can potentially overwrite existing remote images? (Would then need something likeazd deploy --skip-publish
instead ofazd deploy --force-publish
)azd deploy --from-package
now supports fully qualified remote images, so one can run something like:azd publish <svc>
--from-package
provided)azd deploy <svc>
--from-package
provided)