Skip to content

Commit 2bacf69

Browse files
committed
update: docker v2 API
1 parent 9e1618f commit 2bacf69

File tree

3 files changed

+235
-2
lines changed

3 files changed

+235
-2
lines changed

pkg/providers/cloudinit/templates.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ func getDefaultPackages() []string {
152152
"git",
153153
"unzip",
154154
"nginx",
155-
"docker.io",
156-
"docker-compose",
155+
"ca-certificates",
156+
"gnupg",
157157
"certbot",
158158
"python3-certbot-nginx",
159159
"ufw",
@@ -195,6 +195,13 @@ func getDefaultCommands(username, appName string) []string {
195195
return []string{
196196
"systemctl enable nginx",
197197
"systemctl start nginx",
198+
// Install Docker CE with Compose V2 from official repository
199+
"install -m 0755 -d /etc/apt/keyrings",
200+
"curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc",
201+
"chmod a+r /etc/apt/keyrings/docker.asc",
202+
`bash -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list'`,
203+
"apt-get update",
204+
"DEBIAN_FRONTEND=noninteractive apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin",
198205
"systemctl enable docker",
199206
"systemctl start docker",
200207
fmt.Sprintf("usermod -aG docker %s", username),

pkg/runtime/installers/docker.go

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
package installers
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"lightfold/pkg/runtime"
8+
)
9+
10+
type dockerInstaller struct{}
11+
12+
func init() {
13+
Register(&dockerInstaller{})
14+
}
15+
16+
func (d *dockerInstaller) Runtime() runtime.Runtime {
17+
return runtime.RuntimeDocker
18+
}
19+
20+
func (d *dockerInstaller) IsInstalled(ctx *Context) (bool, error) {
21+
// Check if docker compose (V2) is available
22+
// Use sudo since deploy user might not be in docker group yet
23+
result := ctx.SSH.ExecuteSudo("docker compose version 2>/dev/null || echo 'not-found'")
24+
if result.Error != nil {
25+
return false, result.Error
26+
}
27+
28+
output := strings.TrimSpace(result.Stdout)
29+
if output == "not-found" || output == "" {
30+
return false, nil
31+
}
32+
33+
// Should contain "Docker Compose version" for V2
34+
if !strings.Contains(output, "Docker Compose version") {
35+
return false, nil
36+
}
37+
38+
return true, nil
39+
}
40+
41+
func (d *dockerInstaller) Install(ctx *Context) error {
42+
logOutput(ctx, " Installing Docker with Compose V2...")
43+
44+
// Remove old Docker packages that might conflict
45+
d.removeOldDocker(ctx)
46+
47+
// Install prerequisites
48+
if err := d.installPrerequisites(ctx); err != nil {
49+
return err
50+
}
51+
52+
// Add Docker's official GPG key and repository
53+
if err := d.addDockerRepository(ctx); err != nil {
54+
return err
55+
}
56+
57+
// Install Docker Engine with Compose plugin
58+
if err := d.installDockerEngine(ctx); err != nil {
59+
return err
60+
}
61+
62+
// Start and enable Docker service
63+
if err := d.enableDockerService(ctx); err != nil {
64+
return err
65+
}
66+
67+
// Add deploy user to docker group
68+
if err := d.addUserToDockerGroup(ctx); err != nil {
69+
return err
70+
}
71+
72+
// Verify installation
73+
if err := d.verifyInstallation(ctx); err != nil {
74+
return err
75+
}
76+
77+
return nil
78+
}
79+
80+
func (d *dockerInstaller) removeOldDocker(ctx *Context) {
81+
// Remove old Docker packages that use V1 compose syntax
82+
packages := []string{
83+
"docker.io",
84+
"docker-compose",
85+
"docker-doc",
86+
"podman-docker",
87+
"containerd",
88+
"runc",
89+
}
90+
91+
for _, pkg := range packages {
92+
ctx.SSH.ExecuteSudo(fmt.Sprintf("apt-get remove -y %s 2>/dev/null || true", pkg))
93+
}
94+
95+
ctx.SSH.ExecuteSudo("apt-get autoremove -y 2>/dev/null || true")
96+
}
97+
98+
func (d *dockerInstaller) installPrerequisites(ctx *Context) error {
99+
result := ctx.SSH.ExecuteSudo("apt-get update")
100+
if result.Error != nil || result.ExitCode != 0 {
101+
return formatCommandError("failed to update apt", result)
102+
}
103+
104+
prereqs := "ca-certificates curl gnupg"
105+
result = ctx.SSH.ExecuteSudo(fmt.Sprintf("DEBIAN_FRONTEND=noninteractive apt-get install -y %s", prereqs))
106+
if result.Error != nil || result.ExitCode != 0 {
107+
return formatCommandError("failed to install prerequisites", result)
108+
}
109+
110+
return nil
111+
}
112+
113+
func (d *dockerInstaller) addDockerRepository(ctx *Context) error {
114+
// Create keyrings directory
115+
result := ctx.SSH.ExecuteSudo("install -m 0755 -d /etc/apt/keyrings")
116+
if result.Error != nil || result.ExitCode != 0 {
117+
return formatCommandError("failed to create keyrings directory", result)
118+
}
119+
120+
// Download Docker's GPG key
121+
result = ctx.SSH.ExecuteSudo("curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc")
122+
if result.Error != nil || result.ExitCode != 0 {
123+
return formatCommandError("failed to download Docker GPG key", result)
124+
}
125+
126+
result = ctx.SSH.ExecuteSudo("chmod a+r /etc/apt/keyrings/docker.asc")
127+
if result.Error != nil || result.ExitCode != 0 {
128+
return formatCommandError("failed to set GPG key permissions", result)
129+
}
130+
131+
// Add Docker repository
132+
// Wrap in bash -c so the entire pipeline runs under sudo
133+
addRepoCmd := `bash -c 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list'`
134+
result = ctx.SSH.ExecuteSudo(addRepoCmd)
135+
if result.Error != nil || result.ExitCode != 0 {
136+
return formatCommandError("failed to add Docker repository", result)
137+
}
138+
139+
// Update apt with new repository
140+
result = ctx.SSH.ExecuteSudo("apt-get update")
141+
if result.Error != nil || result.ExitCode != 0 {
142+
return formatCommandError("failed to update apt after adding Docker repo", result)
143+
}
144+
145+
return nil
146+
}
147+
148+
func (d *dockerInstaller) installDockerEngine(ctx *Context) error {
149+
packages := "docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin"
150+
result := ctx.SSH.ExecuteSudo(fmt.Sprintf("DEBIAN_FRONTEND=noninteractive apt-get install -y %s", packages))
151+
if ctx.Tail != nil {
152+
ctx.Tail(result, 5)
153+
}
154+
if result.Error != nil || result.ExitCode != 0 {
155+
return formatCommandError("failed to install Docker Engine", result)
156+
}
157+
158+
return nil
159+
}
160+
161+
func (d *dockerInstaller) enableDockerService(ctx *Context) error {
162+
result := ctx.SSH.ExecuteSudo("systemctl enable docker")
163+
if result.Error != nil || result.ExitCode != 0 {
164+
return formatCommandError("failed to enable Docker service", result)
165+
}
166+
167+
result = ctx.SSH.ExecuteSudo("systemctl start docker")
168+
if result.Error != nil || result.ExitCode != 0 {
169+
return formatCommandError("failed to start Docker service", result)
170+
}
171+
172+
return nil
173+
}
174+
175+
func (d *dockerInstaller) addUserToDockerGroup(ctx *Context) error {
176+
// Add deploy user to docker group so they can run docker without sudo
177+
result := ctx.SSH.ExecuteSudo("usermod -aG docker deploy")
178+
if result.Error != nil || result.ExitCode != 0 {
179+
// Non-fatal: user might not exist or might already be in group
180+
logOutput(ctx, " Warning: could not add deploy user to docker group")
181+
}
182+
183+
return nil
184+
}
185+
186+
func (d *dockerInstaller) verifyInstallation(ctx *Context) error {
187+
// Verify docker is installed
188+
result := ctx.SSH.Execute("docker --version")
189+
dockerVersion := strings.TrimSpace(result.Stdout)
190+
if result.Error != nil || result.ExitCode != 0 || dockerVersion == "" {
191+
return formatCommandError("Docker installation verification failed", result)
192+
}
193+
194+
// Verify docker compose V2 is installed
195+
result = ctx.SSH.Execute("docker compose version")
196+
composeVersion := strings.TrimSpace(result.Stdout)
197+
if result.Error != nil || result.ExitCode != 0 || composeVersion == "" {
198+
return formatCommandError("Docker Compose V2 installation verification failed", result)
199+
}
200+
201+
if !strings.Contains(composeVersion, "Docker Compose version") {
202+
return fmt.Errorf("expected Docker Compose V2 but got: %s", composeVersion)
203+
}
204+
205+
logOutput(ctx, fmt.Sprintf(" Docker installed: %s", dockerVersion))
206+
logOutput(ctx, fmt.Sprintf(" %s", composeVersion))
207+
208+
return nil
209+
}

pkg/runtime/types.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const (
1010
RuntimePHP Runtime = "php" // PHP
1111
RuntimeRuby Runtime = "ruby" // Ruby
1212
RuntimeJava Runtime = "java" // Java
13+
RuntimeDocker Runtime = "docker" // Docker + Docker Compose V2
1314
RuntimeUnknown Runtime = "unknown"
1415
)
1516

@@ -36,6 +37,8 @@ func GetRuntimeFromLanguage(language string) Runtime {
3637
return RuntimeRuby
3738
case "Java":
3839
return RuntimeJava
40+
case "Container":
41+
return RuntimeDocker
3942
default:
4043
return RuntimeUnknown
4144
}
@@ -133,6 +136,20 @@ func GetRuntimeInfo(rt Runtime) RuntimeInfo {
133136
Commands: []string{},
134137
}
135138

139+
case RuntimeDocker:
140+
return RuntimeInfo{
141+
Runtime: RuntimeDocker,
142+
Packages: []string{
143+
"docker-ce",
144+
"docker-ce-cli",
145+
"containerd.io",
146+
"docker-buildx-plugin",
147+
"docker-compose-plugin",
148+
},
149+
Directories: []string{},
150+
Commands: []string{},
151+
}
152+
136153
default:
137154
return RuntimeInfo{
138155
Runtime: RuntimeUnknown,

0 commit comments

Comments
 (0)