Skip to content

Commit 2c08f11

Browse files
committed
Always cache apt metadata and downloads outside the image
This change uses cache mounts in the generated Dockerfiles for features to ensure that apt metadata and downloaded packages remain available throughout the build process without ever getting baked into layers. To make life easier for feature implementations, this also runs `apt update` once before the feature build, so that the metadata cache is up-to-date. This has the following benefits: * reduced build-time costs: apt metadata is only downloaded once * smaller layer size: features cannot accidentally include downloaded packages in their layer (see devcontainers/features#1298) * reduced complexity: features do not have to worry about apt caching and cache cleaning * optionally, if building on a caching buildkit service like depot.dev, the cache is re-used across builds, further reducing build-time
1 parent 5c9623c commit 2c08f11

File tree

1 file changed

+37
-25
lines changed

1 file changed

+37
-25
lines changed

src/spec-configuration/containerFeaturesConfiguration.ts

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ FROM $_DEV_CONTAINERS_BASE_IMAGE AS dev_containers_target_stage
211211
212212
USER root
213213
214+
RUN --mount=type=cache,target=/var/cache/apt \\
215+
--mount=type=cache,target=/var/lib/apt/lists \\
216+
apt update
217+
214218
RUN mkdir -p ${FEATURES_CONTAINER_TEMP_DEST_FOLDER}
215219
COPY --from=dev_containers_feature_content_normalize /tmp/build-features/ ${FEATURES_CONTAINER_TEMP_DEST_FOLDER}
216220
@@ -306,20 +310,24 @@ echo "_REMOTE_USER_HOME=$(${getEntPasswdShellCommand(remoteUser)} | cut -d: -f6)
306310
const dest = path.posix.join(FEATURES_CONTAINER_TEMP_DEST_FOLDER, folder!);
307311
if (!useBuildKitBuildContexts) {
308312
result += `COPY --chown=root:root --from=dev_containers_feature_content_source ${source} ${dest}
309-
RUN chmod -R 0755 ${dest} \\
310-
&& cd ${dest} \\
311-
&& chmod +x ./install.sh \\
312-
&& ./install.sh
313+
RUN --mount=type=cache,target=/var/cache/apt \\
314+
--mount=type=cache,target=/var/lib/apt/lists \\
315+
chmod -R 0755 ${dest} \\
316+
&& cd ${dest} \\
317+
&& chmod +x ./install.sh \\
318+
&& ./install.sh
313319
314320
`;
315321
} else {
316-
result += `RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder}${useSELinuxLabel ? ',z' : ''} \\
317-
cp -ar /tmp/build-features-src/${folder} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
318-
&& chmod -R 0755 ${dest} \\
319-
&& cd ${dest} \\
320-
&& chmod +x ./install.sh \\
321-
&& ./install.sh \\
322-
&& rm -rf ${dest}
322+
result += `RUN --mount=type=cache,target=/var/cache/apt \\
323+
--mount=type=cache,target=/var/lib/apt/lists \\
324+
--mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${folder}${useSELinuxLabel ? ',z' : ''} \\
325+
cp -ar /tmp/build-features-src/${folder} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
326+
&& chmod -R 0755 ${dest} \\
327+
&& cd ${dest} \\
328+
&& chmod +x ./install.sh \\
329+
&& ./install.sh \\
330+
&& rm -rf ${dest}
323331
324332
`;
325333
}
@@ -333,21 +341,25 @@ RUN chmod -R 0755 ${dest} \\
333341
if (!useBuildKitBuildContexts) {
334342
result += `
335343
COPY --chown=root:root --from=dev_containers_feature_content_source ${source} ${dest}
336-
RUN chmod -R 0755 ${dest} \\
337-
&& cd ${dest} \\
338-
&& chmod +x ./devcontainer-features-install.sh \\
339-
&& ./devcontainer-features-install.sh
344+
RUN --mount=type=cache,target=/var/cache/apt \\
345+
--mount=type=cache,target=/var/lib/apt/lists \\
346+
chmod -R 0755 ${dest} \\
347+
&& cd ${dest} \\
348+
&& chmod +x ./devcontainer-features-install.sh \\
349+
&& ./devcontainer-features-install.sh
340350
341351
`;
342352
} else {
343353
result += `
344-
RUN --mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId}${useSELinuxLabel ? ',z' : ''} \\
345-
cp -ar /tmp/build-features-src/${feature.consecutiveId} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
346-
&& chmod -R 0755 ${dest} \\
347-
&& cd ${dest} \\
348-
&& chmod +x ./devcontainer-features-install.sh \\
349-
&& ./devcontainer-features-install.sh \\
350-
&& rm -rf ${dest}
354+
RUN --mount=type=cache,target=/var/cache/apt \\
355+
--mount=type=cache,target=/var/lib/apt/lists \\
356+
--mount=type=bind,from=dev_containers_feature_content_source,source=${source},target=/tmp/build-features-src/${feature.consecutiveId}${useSELinuxLabel ? ',z' : ''} \\
357+
cp -ar /tmp/build-features-src/${feature.consecutiveId} ${FEATURES_CONTAINER_TEMP_DEST_FOLDER} \\
358+
&& chmod -R 0755 ${dest} \\
359+
&& cd ${dest} \\
360+
&& chmod +x ./devcontainer-features-install.sh \\
361+
&& ./devcontainer-features-install.sh \\
362+
&& rm -rf ${dest}
351363
352364
`;
353365
}
@@ -467,7 +479,7 @@ function updateFromOldProperties<T extends { features: (Feature & { extensions?:
467479
};
468480
}
469481

470-
// Generate a base featuresConfig object with the set of locally-cached features,
482+
// Generate a base featuresConfig object with the set of locally-cached features,
471483
// as well as downloading and merging in remote feature definitions.
472484
export async function generateFeaturesConfig(params: ContainerFeatureInternalParams, dstFolder: string, config: DevContainerConfig, additionalFeatures: Record<string, string | boolean | Record<string, string | boolean>>) {
473485
const { output } = params;
@@ -931,7 +943,7 @@ export async function processFeatureIdentifier(params: CommonParams, configPath:
931943
};
932944
return newFeaturesSet;
933945
} else {
934-
// We must have a tag, return a tarball URI for the tagged version.
946+
// We must have a tag, return a tarball URI for the tagged version.
935947
let newFeaturesSet: FeatureSet = {
936948
sourceInformation: {
937949
type: 'github-repo',
@@ -1151,7 +1163,7 @@ export async function fetchContentsAtTarballUri(params: { output: Log; env: Node
11511163

11521164
// Reads the feature's 'devcontainer-feature.json` and applies any attributes to the in-memory Feature object.
11531165
// NOTE:
1154-
// Implements the latest ('internalVersion' = '2') parsing logic,
1166+
// Implements the latest ('internalVersion' = '2') parsing logic,
11551167
// Falls back to earlier implementation(s) if requirements not present.
11561168
// Returns a boolean indicating whether the feature was successfully parsed.
11571169
async function applyFeatureConfigToFeature(output: Log, featureSet: FeatureSet, feature: Feature, featCachePath: string, computedDigest: string | undefined): Promise<boolean> {

0 commit comments

Comments
 (0)