├── .envrc ├── .gitmodules ├── README.md ├── compose.go ├── go.mod ├── go.sum ├── redis.yml └── wordpress.yml /.envrc: -------------------------------------------------------------------------------- 1 | export _EXPERIMENTAL_DAGGER_CLI_BIN=/home/vito/src/dagger/bin/dagger 2 | export _EXPERIMENTAL_DAGGER_RUNNER_HOST=docker-container://dagger-engine.dev 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dagger"] 2 | path = dagger 3 | url = https://github.com/vito/dagger 4 | branch = h2c-networking 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dagger Compose 2 | 3 | Runs `docker-compose.yml`... but in Dagger. 📢💨📢💨 (those are airhorns) 4 | 5 | Currently uses a proof-of-concept host-to-container implementation. 6 | 7 | ## Example 8 | 9 | The included `wordpress.yml` runs WordPress, published to port 8080 on the 10 | host. 11 | 12 | ```sh 13 | go run . wordpress.yml 14 | ``` 15 | 16 | ## Thanks 17 | 18 | Thanks to [**@marcosnils**](https://github.com/marcosnils) for the idea! 19 | -------------------------------------------------------------------------------- /compose.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os" 7 | "sort" 8 | "strconv" 9 | 10 | "dagger.io/dagger" 11 | "github.com/compose-spec/compose-go/cli" 12 | "github.com/compose-spec/compose-go/types" 13 | "golang.org/x/sync/errgroup" 14 | ) 15 | 16 | func main() { 17 | ctx := context.Background() 18 | 19 | opts, err := cli.NewProjectOptions(os.Args[1:], 20 | cli.WithWorkingDirectory("."), 21 | cli.WithDefaultConfigPath, 22 | cli.WithOsEnv, 23 | cli.WithConfigFileEnv, 24 | ) 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | project, err := cli.ProjectFromOptions(opts) 30 | if err != nil { 31 | panic(err) 32 | } 33 | 34 | c, err := dagger.Connect(ctx, dagger.WithLogOutput(os.Stderr)) 35 | if err != nil { 36 | panic(err) 37 | } 38 | defer c.Close() 39 | 40 | eg := new(errgroup.Group) 41 | 42 | for _, svc := range project.Services { 43 | daggerSvc, err := serviceContainer(c, project, svc) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | eg.Go(func() error { 49 | _, err := daggerSvc.Start(ctx) 50 | return err 51 | }) 52 | 53 | for _, port := range daggerSvc.PublishedPorts { 54 | proxy := daggerSvc.Proxy(port.Address, dagger.ServiceProxyOpts{ 55 | ServicePort: port.Target, 56 | Protocol: port.Protocol, 57 | }) 58 | 59 | eg.Go(func() error { 60 | _, err := proxy.Start(ctx) 61 | return err 62 | }) 63 | } 64 | } 65 | 66 | err = eg.Wait() 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | <-ctx.Done() 72 | } 73 | 74 | type Service struct { 75 | *dagger.Service 76 | 77 | PublishedPorts []PublishedPort 78 | } 79 | 80 | type PublishedPort struct { 81 | Address string 82 | Target int 83 | Protocol dagger.NetworkProtocol 84 | } 85 | 86 | func serviceContainer(c *dagger.Client, project *types.Project, svc types.ServiceConfig) (*Service, error) { 87 | ctr := c.Pipeline(svc.Name).Container() 88 | if svc.Image != "" { 89 | ctr = ctr.From(svc.Image) 90 | } else if svc.Build != nil { 91 | args := []dagger.BuildArg{} 92 | for name, val := range svc.Build.Args { 93 | if val != nil { 94 | args = append(args, dagger.BuildArg{ 95 | Name: name, 96 | Value: *val, 97 | }) 98 | } 99 | } 100 | 101 | ctr = ctr.Build(c.Host().Directory(svc.Build.Context), dagger.ContainerBuildOpts{ 102 | Dockerfile: svc.Build.Dockerfile, 103 | BuildArgs: args, 104 | Target: svc.Build.Target, 105 | }) 106 | } 107 | 108 | // sort env to ensure same container 109 | type env struct{ name, value string } 110 | envs := []env{} 111 | for name, val := range svc.Environment { 112 | if val != nil { 113 | envs = append(envs, env{name, *val}) 114 | } 115 | } 116 | sort.Slice(envs, func(i, j int) bool { 117 | return envs[i].name < envs[j].name 118 | }) 119 | for _, env := range envs { 120 | ctr = ctr.WithEnvVariable(env.name, env.value) 121 | } 122 | 123 | published := []PublishedPort{} 124 | for _, port := range svc.Ports { 125 | switch port.Mode { 126 | case "ingress": 127 | publishedPort, err := strconv.Atoi(port.Published) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | ctr = ctr.WithExposedPort(int(port.Target)) 133 | 134 | protocol := dagger.Tcp 135 | switch port.Protocol { 136 | case "udp": 137 | protocol = dagger.Udp 138 | case "", "tcp": 139 | protocol = dagger.Tcp 140 | default: 141 | return nil, fmt.Errorf("protocol %s not supported", port.Protocol) 142 | } 143 | 144 | published = append(published, PublishedPort{ 145 | Address: fmt.Sprintf(":%d", publishedPort), 146 | Target: int(port.Target), 147 | Protocol: protocol, 148 | }) 149 | default: 150 | return nil, fmt.Errorf("port mode %s not supported", port.Mode) 151 | } 152 | } 153 | 154 | for _, expose := range svc.Expose { 155 | port, err := strconv.Atoi(expose) 156 | if err != nil { 157 | return nil, err 158 | } 159 | 160 | ctr = ctr.WithExposedPort(port) 161 | } 162 | 163 | for _, vol := range svc.Volumes { 164 | switch vol.Type { 165 | case types.VolumeTypeBind: 166 | ctr = ctr.WithMountedDirectory(vol.Target, c.Host().Directory(vol.Source)) 167 | case types.VolumeTypeVolume: 168 | ctr = ctr.WithMountedCache(vol.Target, c.CacheVolume(vol.Source)) 169 | default: 170 | return nil, fmt.Errorf("volume type %s not supported", vol.Type) 171 | } 172 | } 173 | 174 | for depName := range svc.DependsOn { 175 | cfg, err := project.GetService(depName) 176 | if err != nil { 177 | return nil, err 178 | } 179 | 180 | svc, err := serviceContainer(c, project, cfg) 181 | if err != nil { 182 | return nil, err 183 | } 184 | 185 | ctr = ctr.WithServiceBinding(depName, svc.Service) 186 | } 187 | 188 | var opts dagger.ContainerWithExecOpts 189 | if svc.Privileged { 190 | opts.InsecureRootCapabilities = true 191 | } 192 | 193 | ctr = ctr.WithExec(svc.Command, opts) 194 | 195 | return &Service{ 196 | Service: ctr.Service(), 197 | PublishedPorts: published, 198 | }, nil 199 | } 200 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/vito/dagger-compose 2 | 3 | go 1.20 4 | 5 | require ( 6 | dagger.io/dagger v0.4.6 7 | github.com/compose-spec/compose-go v1.12.0 8 | golang.org/x/sync v0.3.0 9 | ) 10 | 11 | require ( 12 | github.com/99designs/gqlgen v0.17.31 // indirect 13 | github.com/Khan/genqlient v0.6.0 // indirect 14 | github.com/adrg/xdg v0.4.0 // indirect 15 | github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa // indirect 16 | github.com/docker/go-connections v0.4.0 // indirect 17 | github.com/docker/go-units v0.5.0 // indirect 18 | github.com/iancoleman/strcase v0.3.0 // indirect 19 | github.com/imdario/mergo v0.3.13 // indirect 20 | github.com/mattn/go-shellwords v1.0.12 // indirect 21 | github.com/mitchellh/mapstructure v1.5.0 // indirect 22 | github.com/opencontainers/go-digest v1.0.0 // indirect 23 | github.com/pkg/errors v0.9.1 // indirect 24 | github.com/sirupsen/logrus v1.9.0 // indirect 25 | github.com/vektah/gqlparser/v2 v2.5.6 // indirect 26 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect 27 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect 28 | github.com/xeipuuv/gojsonschema v1.2.0 // indirect 29 | golang.org/x/mod v0.12.0 // indirect 30 | golang.org/x/sys v0.10.0 // indirect 31 | golang.org/x/tools v0.11.0 // indirect 32 | gopkg.in/yaml.v2 v2.4.0 // indirect 33 | ) 34 | 35 | replace dagger.io/dagger => ./dagger/sdk/go 36 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/gqlgen v0.17.31 h1:VncSQ82VxieHkea8tz11p7h/zSbvHSxSDZfywqWt158= 2 | github.com/99designs/gqlgen v0.17.31/go.mod h1:i4rEatMrzzu6RXaHydq1nmEPZkb3bKQsnxNRHS4DQB4= 3 | github.com/Khan/genqlient v0.6.0 h1:Bwb1170ekuNIVIwTJEqvO8y7RxBxXu639VJOkKSrwAk= 4 | github.com/Khan/genqlient v0.6.0/go.mod h1:rvChwWVTqXhiapdhLDV4bp9tz/Xvtewwkon4DpWWCRM= 5 | github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= 6 | github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= 7 | github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8= 8 | github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= 9 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= 10 | github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= 11 | github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= 12 | github.com/compose-spec/compose-go v1.12.0 h1:MSyWW//yijispnqmTqJSMv1ptRTlKU1sLPHJdc2ACnA= 13 | github.com/compose-spec/compose-go v1.12.0/go.mod h1:0/X/dTehChV+KBB696nOOl+HYzKn+XaIm4i12phUB5U= 14 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 15 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 16 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 17 | github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= 18 | github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa h1:L9Ay/slwQ4ERSPaurC+TVkZrM0K98GNrEEo1En3e8as= 19 | github.com/distribution/distribution/v3 v3.0.0-20230214150026-36d8c594d7aa/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= 20 | github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= 21 | github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= 22 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 23 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 24 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 25 | github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= 26 | github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= 27 | github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= 28 | github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 29 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 30 | github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= 31 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 32 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 33 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 34 | github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= 35 | github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= 36 | github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= 37 | github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= 38 | github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= 39 | github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= 40 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 41 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 42 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 43 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 44 | github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= 45 | github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= 46 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 47 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 48 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 49 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 50 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 51 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 52 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 53 | github.com/vektah/gqlparser/v2 v2.5.6 h1:Ou14T0N1s191eRMZ1gARVqohcbe1e8FrcONScsq8cRU= 54 | github.com/vektah/gqlparser/v2 v2.5.6/go.mod h1:z8xXUff237NntSuH8mLFijZ+1tjV1swDbpDqjJmk6ME= 55 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= 56 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 57 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 58 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 59 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 60 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 61 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 62 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 63 | golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= 64 | golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= 65 | golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 67 | golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= 68 | golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 69 | golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= 70 | golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= 71 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 72 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= 73 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 74 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 75 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 76 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 77 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 78 | gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 79 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 80 | gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= 81 | -------------------------------------------------------------------------------- /redis.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | redis: 5 | image: redis 6 | ports: 7 | - "6379:6379" 8 | -------------------------------------------------------------------------------- /wordpress.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | # We use a mariadb image which supports both amd64 & arm64 architecture 4 | image: mariadb:10.6.4-focal 5 | # If you really want to use MySQL, uncomment the following line 6 | #image: mysql:8.0.27 7 | command: '--default-authentication-plugin=mysql_native_password' 8 | volumes: 9 | - db_data:/var/lib/mysql 10 | restart: always 11 | environment: 12 | - MYSQL_ROOT_PASSWORD=somewordpress 13 | - MYSQL_DATABASE=wordpress 14 | - MYSQL_USER=wordpress 15 | - MYSQL_PASSWORD=wordpress 16 | expose: 17 | - 3306 18 | wordpress: 19 | image: wordpress:latest 20 | volumes: 21 | - wp_data:/var/www/html 22 | ports: 23 | - 8080:80 24 | restart: always 25 | depends_on: [db] 26 | environment: 27 | - WORDPRESS_DB_HOST=db 28 | - WORDPRESS_DB_USER=wordpress 29 | - WORDPRESS_DB_PASSWORD=wordpress 30 | - WORDPRESS_DB_NAME=wordpress 31 | volumes: 32 | db_data: 33 | wp_data: 34 | --------------------------------------------------------------------------------