Skip to content
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

Use a docker registry for cache #283

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 103 additions & 1 deletion .castor/docker.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use function Castor\open;
use function Castor\run;
use function Castor\variable;
use function Castor\yaml_parse;
lyrixx marked this conversation as resolved.
Show resolved Hide resolved

#[AsTask(description: 'Displays some help and available urls for the current project', namespace: '')]
function about(): void
Expand Down Expand Up @@ -84,7 +85,6 @@ function build(
$command = [
...$command,
'build',
'--build-arg', 'USER_ID=' . variable('user_id'),
'--build-arg', 'PHP_VERSION=' . variable('php_version'),
'--build-arg', 'PROJECT_NAME=' . variable('project_name'),
];
Expand Down Expand Up @@ -295,6 +295,107 @@ function workers_stop(): void
stop(profiles: ['worker']);
}

#[AsTask(description: 'Push images cache to the registry', namespace: 'docker', name: 'push', aliases: ['push'])]
function push(): void
{
// Generate bake file
$composeFile = variable('docker_compose_files');
$composeFile[] = 'docker-compose.builder.yml';

$targets = [];

/** @var string|null $registry */
$registry = variable('registry');

if ($registry === null || $registry === '') {
throw new \RuntimeException('You must define a registry to push images.');
}

foreach ($composeFile as $file) {
$path = variable('root_dir') . '/infrastructure/docker/' . $file;
$content = file_get_contents($path);
$data = yaml_parse($content);

foreach ($data['services'] ?? [] as $service => $config) {
$cacheFrom = $config['build']['cache_from'][0] ?? null;

if (null === $cacheFrom) {
continue;
}

$cacheFrom = explode(',', $cacheFrom);
$reference = null;
$type = null;

if (count($cacheFrom) === 1) {
$reference = $cacheFrom[0];
$type = 'registry';
} else {
foreach ($cacheFrom as $part) {
$from = explode('=', $part);

if (count($from) !== 2) {
continue;
}

if ($from[0] === 'type') {
$type = $from[1];
}

if ($from[0] === 'ref') {
$reference = $from[1];
}
}
}

$targets[] = [
'reference' => $reference,
'type' => $type,
'context' => $config['build']['context'],
'dockerfile' => $config['build']['dockerfile'] ?? 'Dockerfile',
'target' => $config['build']['target'] ?? null,
];
}
}

$content = sprintf(<<<EOHCL
group "default" {
targets = [%s]
}


EOHCL
, implode(', ', array_map(fn ($target) => sprintf('"%s"', $target['target']), $targets)));


foreach ($targets as $target) {
$reference = str_replace('${REGISTRY:-}', $registry, $target['reference'] ?? '');

$content .= sprintf(<<<EOHCL
target "%s" {
context = "infrastructure/docker/%s"
dockerfile = "%s"
cache-from = ["%s"]
cache-to = ["type=%s,ref=%s,mode=max"]
target = "%s"
args = {
PHP_VERSION = "%s"
}
}


EOHCL
, $target['target'], $target['context'], $target['dockerfile'], $reference, $target['type'], $reference, $target['target'], variable('php_version'));
}

// write bake file in tmp file
$bakeFile = tempnam(sys_get_temp_dir(), 'bake');
file_put_contents($bakeFile, $content);

// Run bake
run(['docker', 'buildx', 'bake', '-f', $bakeFile]);
}

#[AsContext(default: true)]
function create_default_context(): Context
{
Expand Down Expand Up @@ -401,6 +502,7 @@ function docker_compose(array $subCommand, ?Context $c = null, bool $withBuilder
'USER_ID' => variable('user_id'),
'COMPOSER_CACHE_DIR' => variable('composer_cache_dir'),
'PHP_VERSION' => variable('php_version'),
'REGISTRY' => variable('registry'),
])
;

Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/cache.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Push docker image to registry

"on":
push:
# Only run this job when pushing to the main branch
branches: ["main"]

permissions:
contents: read
packages: write

env:
DS_REGISTRY: "ghcr.io/jolicode/docker-starter"
DS_PHP_VERSION: "8.3"

jobs:
push-images:
name: Push image to registry
runs-on: ubuntu-latest
env:
DS_PHP_VERSION: "8.3"
steps:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2

- uses: actions/checkout@v4

- name: setup-castor
uses: castor-php/setup-castor@v0.1.0

- name: Log in to registry
shell: bash
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin

- name: "Build and start the infrastructure"
run: "castor docker:push"
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ name: Continuous Integration

permissions:
contents: read
packages: read

env:
# Fix for symfony/color detection. We know GitHub Actions can handle it
ANSICON: 1
CASTOR_CONTEXT: ci
DS_REGISTRY: "ghcr.io/jolicode/docker-starter"

jobs:
check-dockerfiles:
Expand All @@ -41,6 +43,20 @@ jobs:
steps:
- uses: actions/checkout@v4

# Install official version of docker that correctly supports from-cache option in docker compose
lyrixx marked this conversation as resolved.
Show resolved Hide resolved
- name: Set up Docker
uses: crazy-max/ghaction-setup-docker@v3
with:
set-host: true

# Docker socket path is different when using setup docker
- name: Set Docker Socket Host
run: echo "DOCKER_SOCKET_PATH=${DOCKER_HOST:5}" >> $GITHUB_ENV

- name: Log in to registry
shell: bash
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin

- name: setup-castor
uses: castor-php/setup-castor@v0.1.0

Expand Down
2 changes: 2 additions & 0 deletions .home/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/*
!.gitignore
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -957,6 +957,128 @@ services:

</details>

### How to use a docker registry to cache images layer

<details>

<summary>Read the cookbook</summary>

You can use a docker registry to cache images layer, it can be useful to speed up the build process during the CI and
local development.

First you need a docker registry, in following examples we will use the GitHub registry (ghcr.io).

Then add the registry to the context variable of the `castor.php` file:

```php
function create_default_variables(): Context
{
return [
// [...]
'registry' => 'ghcr.io/your-organization/your-project',
];
}
```

Once you have the registry, you can push the images to the registry:

```bash
castor docker:push
```

> [!WARNING] Pushing images cache from a dev environment to a registry is not recommended, as cache is highly sensitive
> to the environment and may not be compatible with other environments. It is recommended to push the cache from the CI
> environment.

This command will generate a bake file with the images to push from the `cache_from` directive of the `docker-compose.yml` file.
If you want to add more images to push, you can add the `cache_from` directive to them.

```yaml
services:
my-service:
build:
cache_from:
- "type=registry,ref=${REGISTRY:-}/my-service:cache"
```
</details>

### How to use cached images in a GitHub action
joelwurtz marked this conversation as resolved.
Show resolved Hide resolved

<details>

<summary>Read the cookbook</summary>

If you are using a GitHub action to build your images, you can use the cached images from the registry to speed up the build process.
However there are few steps to make it works nicely due to the docker binary limitations in GitHub actions.

#### Pushing images to the registry from a GitHub action

To push images to the registry in a github action you will need to do this :

1. Ensure that the github token have the `write:packages` scope.

```yaml
permissions:
contents: read
packages: write
```


2. Install Docker buildx in the github action

```yaml
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
```

3. Login to the registry

```yaml
- name: Log in to registry
shell: bash
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
```

#### Using the cached images in GitHub action

Images layers built when using the Docker Buildx will have a different hash than the one built with the classic Docker build.
Then you will need to use a more recent version of the Docker binary to use the cached images by either:

* Use buildx in each GitHub action workflow step

```yaml
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
```

* Use the official Docker binary
```yaml
# Install official version of docker that correctly supports from-cache option in docker compose
- name: Set up Docker
uses: crazy-max/ghaction-setup-docker@v3
with:
set-host: true

# Docker socket path is different when using setup docker
- name: Set Docker Socket Host
run: echo "DOCKER_SOCKET_PATH=${DOCKER_HOST:5}" >> $GITHUB_ENV
```

The second option is faster (there is no need to transfer images between buildx and local docker), but it is not
officially supported by GitHub actions and may break in the future.

* Login to the registry

```yaml
- name: Log in to registry
shell: bash
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
```

By default images are private in the GitHub registry, you will need to login to the registry to pull the images.

</details>

## Credits

- Created at [JoliCode](https://jolicode.com/)
Expand Down
1 change: 1 addition & 0 deletions castor.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function create_default_variables(): array
"www.{$projectName}.{$tld}",
],
'php_version' => $_SERVER['DS_PHP_VERSION'] ?? '8.3',
'registry' => $_SERVER['DS_REGISTRY'] ?? null,
];
}

Expand Down
10 changes: 5 additions & 5 deletions infrastructure/docker/docker-compose.builder.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
volumes:
builder-data: {}

services:
builder:
build:
context: services/php
target: builder
cache_from:
- "type=registry,ref=${REGISTRY:-}/builder:cache"
depends_on:
- postgres
user: "${USER_ID}:${USER_ID}"
environment:
- COMPOSER_MEMORY_LIMIT=-1
- UID=${USER_ID}
Expand All @@ -17,9 +17,9 @@ services:
- CONTINUOUS_INTEGRATION # Travis CI, Cirrus CI
- BUILD_NUMBER # Jenkins, TeamCity
- RUN_ID # TaskCluster, dsari
- HOME=/home/app
volumes:
- "builder-data:/home/app"
- "${COMPOSER_CACHE_DIR}:/home/app/.composer/cache"
- "../..:/var/www:cached"
- "../../.home:/home/app:cached"
lyrixx marked this conversation as resolved.
Show resolved Hide resolved
profiles:
- default
6 changes: 6 additions & 0 deletions infrastructure/docker/docker-compose.worker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ x-services-templates:
build:
context: services/php
target: worker
cache_from:
- "${REGISTRY:-}/worker:cache"
depends_on:
- postgres
#- rabbitmq
user: "${USER_ID}:${USER_ID}"
volumes:
- "../..:/var/www:cached"
- "../../.home:/home/app:cached"
environment:
- HOME=/home/app
profiles:
- default
- worker
Expand Down
Loading