1 00:00:00,020 --> 00:00:05,739 Sometimes you just need that one file, and it'd be really nice if you just had a simple application 2 00:00:06,060 --> 00:00:09,100 that ran in a web browser that gave you access to your files. 3 00:00:09,700 --> 00:00:12,639 Well, today we're going to be taking a look at CopyParty. 4 00:00:13,080 --> 00:00:16,120 This turns almost any device into a file server. 5 00:00:16,520 --> 00:00:20,580 It's a really simple Python application that did the rounds a few months ago. 6 00:00:21,080 --> 00:00:23,180 It's just taken me a while to get around to covering it. 7 00:00:23,800 --> 00:00:26,760 And this application we're going to run in a container. 8 00:00:26,920 --> 00:00:30,620 We're going to put on our tail nets, of course, because that's what we do here on the TailScale channel. 9 00:00:31,140 --> 00:00:37,080 I'm also going to show you some new Docker Compose secrets, tricks of how you can configure 10 00:00:37,860 --> 00:00:42,379 not only the CopyParty application itself directly in your Compose YAML files, 11 00:00:42,810 --> 00:00:48,600 but also the TailScaleServe sidecar piece directly in your Docker Compose YAMLs as well. 12 00:00:49,260 --> 00:00:53,160 Now, I'll put a link in the description to all of the materials about CopyParty, 13 00:00:53,240 --> 00:00:56,860 such as this fantastic YouTube video, 16 minutes long, 14 00:00:56,910 --> 00:00:58,360 it goes through all of the different features 15 00:00:58,540 --> 00:00:59,420 that CopyParty has. 16 00:00:59,880 --> 00:01:00,380 It's brilliant. 17 00:01:00,550 --> 00:01:02,260 It does a better job than I could possibly manage. 18 00:01:02,980 --> 00:01:04,039 We're going to focus, like I say, 19 00:01:04,180 --> 00:01:05,740 on the Docker Compose aspects today 20 00:01:06,340 --> 00:01:07,960 and deploying this thing onto your tail net. 21 00:01:09,860 --> 00:01:11,580 Now, you've seen on the channel many times before 22 00:01:11,760 --> 00:01:14,680 that I deploy applications with the main application 23 00:01:15,320 --> 00:01:17,759 and then a tailscale sidecar container. 24 00:01:18,560 --> 00:01:19,860 And the downside of that, in fact, 25 00:01:19,870 --> 00:01:22,060 if you look in this long list of apps 26 00:01:22,080 --> 00:01:26,880 we've covered on this channel over the years. The downside of that is that you need this kind of 27 00:01:27,120 --> 00:01:34,020 sidecar volume mounted serve.json file. It's a very simple file. All it essentially does is it tells 28 00:01:34,120 --> 00:01:41,420 the tailscale serve to listen on port 443 to proxy this port from inside the container onto your 29 00:01:41,420 --> 00:01:48,259 tail net using tailscale serve. I've always wished that it was a simple way to do that without bind 30 00:01:48,280 --> 00:01:55,260 mounting a separate file well i found one i found one it's been right under my nose this whole time 31 00:01:56,020 --> 00:02:02,979 docker compose has this configs top level element built into the yaml configuration schema of docker 32 00:02:03,100 --> 00:02:08,820 compose so what we can do is we can define okay this is just the json blob we looked at earlier 33 00:02:09,038 --> 00:02:15,400 kind of minified ish so that it fits more comfortably inside our compose file and doesn't 34 00:02:15,400 --> 00:02:16,440 like WordWrap or anything. 35 00:02:17,200 --> 00:02:19,579 What we can do is we can define these elements 36 00:02:20,130 --> 00:02:21,400 inside our config files, 37 00:02:22,050 --> 00:02:23,780 and then we can reference them later on 38 00:02:24,120 --> 00:02:24,980 inside our configurations. 39 00:02:25,980 --> 00:02:27,519 That's it, that's the cool trick. 40 00:02:28,080 --> 00:02:32,060 So what this does is it ingests this content block up here, 41 00:02:32,280 --> 00:02:33,460 lines four through eight, 42 00:02:34,020 --> 00:02:35,960 and then spits it out into a file 43 00:02:36,430 --> 00:02:37,860 inside the remote container. 44 00:02:38,580 --> 00:02:39,460 So let's take a little look 45 00:02:39,540 --> 00:02:41,459 at the copy party application itself. 46 00:02:42,220 --> 00:02:43,220 It is super basic. 47 00:02:43,540 --> 00:02:46,900 It's just a file browser that runs inside a web browser. 48 00:02:47,280 --> 00:02:49,360 Effectively, you know, I can select an image, 49 00:02:49,940 --> 00:02:51,579 I can view an image, I can go through here 50 00:02:51,620 --> 00:02:54,040 and look at my obsession with the Volkswagen Golf. 51 00:02:54,680 --> 00:02:57,240 This was Wookiees in the Woods, by the way, a few years ago. 52 00:02:57,400 --> 00:02:59,960 Fantastic event if you're a Volkswagen enthusiast like me. 53 00:03:01,180 --> 00:03:03,140 And you know, I can see all of my images 54 00:03:03,360 --> 00:03:04,340 that I've taken over the years. 55 00:03:04,520 --> 00:03:05,820 In fact, these are the same images 56 00:03:05,980 --> 00:03:08,820 that were powering the image video that came out last week. 57 00:03:09,020 --> 00:03:11,280 So I'm gonna log in now using the username and password 58 00:03:11,660 --> 00:03:12,799 configured in my compose file. 59 00:03:13,000 --> 00:03:15,040 It literally right now is change me. 60 00:03:16,840 --> 00:03:18,900 And now I just want to create a new directory. 61 00:03:18,960 --> 00:03:20,240 I'm just going to call this test 62 00:03:20,380 --> 00:03:22,160 because I want to show you how fast this thing is. 63 00:03:23,480 --> 00:03:27,540 I'm going to bring up a directory full of a bunch of test photos. 64 00:03:28,600 --> 00:03:32,920 And then if I just drop this into the browser like so, ready? 65 00:03:33,640 --> 00:03:34,079 Here we go. 66 00:03:34,840 --> 00:03:38,460 We're uploading, I don't know, 25 files, 22 files. 67 00:03:39,160 --> 00:03:39,880 And that's it, it's done. 68 00:03:40,200 --> 00:03:42,560 And we can now bring these up full screen and have a look at them. 69 00:03:42,700 --> 00:03:43,540 We can download them. 70 00:03:43,540 --> 00:03:44,299 We can fetal them. 71 00:03:44,320 --> 00:03:45,239 We can rotate them. 72 00:03:45,520 --> 00:03:46,720 It also supports rich media. 73 00:03:46,760 --> 00:03:49,679 So if you've got an audio file, for example, let me look on my desktop, 74 00:03:50,500 --> 00:03:53,640 where I've almost certainly got a recording that I'm using for this video. 75 00:03:54,240 --> 00:03:55,380 Let me just upload that here. 76 00:03:56,560 --> 00:03:59,060 And we can play those audio files in the browser. 77 00:04:00,540 --> 00:04:00,940 There we go. 78 00:04:01,120 --> 00:04:05,500 So now I could use this as a low-rent podcast player if I wanted to. 79 00:04:06,600 --> 00:04:08,920 The other option it does as well, can I see? 80 00:04:09,160 --> 00:04:13,020 Yeah, it renders the spectral waveform of the file. 81 00:04:13,200 --> 00:04:14,679 I just think that's a really cool, 82 00:04:14,980 --> 00:04:16,899 nice little touch from CopyParty. 83 00:04:17,760 --> 00:04:19,500 There are tons and tons and tons 84 00:04:19,579 --> 00:04:21,160 of other features to this thing. 85 00:04:21,420 --> 00:04:22,860 For example, if I wanted to download 86 00:04:22,980 --> 00:04:24,600 the entire directory as a zip file, 87 00:04:25,200 --> 00:04:26,219 which can be really handy sometimes, 88 00:04:26,480 --> 00:04:27,900 I just click this little button down here. 89 00:04:28,600 --> 00:04:30,620 No right-click menus, no doing silliness, 90 00:04:31,080 --> 00:04:31,640 and there you go. 91 00:04:31,800 --> 00:04:34,140 There's my test directory now as a zip file 92 00:04:34,600 --> 00:04:35,520 on my local system. 93 00:04:35,820 --> 00:04:38,400 There is a way you can mount CopyParty as a webdav, 94 00:04:38,540 --> 00:04:40,320 So if you go up to this option up here and say connect, 95 00:04:41,060 --> 00:04:46,340 you can basically turn your copy party instance into like a remote file server 96 00:04:47,020 --> 00:04:50,159 that appears as a mounted network share inside your computer. 97 00:04:50,330 --> 00:04:53,719 And you can, of course, do this all over TailScale when it's on your TailNet. 98 00:04:53,980 --> 00:04:56,040 And he supports multiple different ways. 99 00:04:56,160 --> 00:04:58,720 You've got WebDAV, you've got PartyFuse, iShare, 100 00:04:59,660 --> 00:05:02,560 as well as all the different operating systems using Rclone 101 00:05:02,880 --> 00:05:05,260 and all the other stuff that exists in the world. 102 00:05:06,060 --> 00:05:08,380 I can't say enough good things about this app. 103 00:05:08,440 --> 00:05:09,560 Honestly, it's really simple. 104 00:05:10,320 --> 00:05:12,480 It's really simple considering it's just a bunch of Python. 105 00:05:14,020 --> 00:05:16,900 And it just does what it needs to do really well. 106 00:05:18,000 --> 00:05:20,240 So let's take a little look at some of the lifecycle stuff 107 00:05:20,640 --> 00:05:23,340 around the config stanzas inside Docker Compose. 108 00:05:23,760 --> 00:05:25,400 You can see that, as I showed you earlier, 109 00:05:25,580 --> 00:05:27,940 we have the tailscale serve configuration, 110 00:05:28,540 --> 00:05:31,620 but we also have the copy party configuration as well. 111 00:05:31,900 --> 00:05:33,479 It's a very simple application to configure. 112 00:05:33,980 --> 00:05:36,500 And so I thought it was a perfect example for today's video. 113 00:05:37,160 --> 00:05:42,800 You can see how the config for TS serve gets reused later on in the file just down here. 114 00:05:43,480 --> 00:05:47,960 And then the same thing applies also to the copy party configuration as well. 115 00:05:48,460 --> 00:05:50,680 So how does this get passed through on the remote side? 116 00:05:50,740 --> 00:05:52,300 What does it look like inside the container? 117 00:05:52,780 --> 00:05:55,799 Well, let's take a look at the copy party TS serve container. 118 00:05:55,840 --> 00:05:58,280 We're going to look inside copy party TS. 119 00:05:59,040 --> 00:06:02,340 Now I've SSH'd into fake NAS, which is where this application is actually running. 120 00:06:02,980 --> 00:06:06,380 And I'm going to do this command, docker exec copy party TS. 121 00:06:06,980 --> 00:06:09,400 and I want to see what the config looks like, 122 00:06:09,400 --> 00:06:12,380 the serve config looks like inside that remote container. 123 00:06:13,000 --> 00:06:15,080 Because if we look at the YAML file look, 124 00:06:15,620 --> 00:06:18,540 this gets passed through and mapped almost as a target 125 00:06:19,080 --> 00:06:23,080 in /config/serve.json, which is the file path here 126 00:06:23,080 --> 00:06:25,240 that I'm going to print out to the terminal 127 00:06:25,360 --> 00:06:26,020 and we're going to have a look at. 128 00:06:26,680 --> 00:06:30,620 And you can see this block here matches perfectly 129 00:06:31,300 --> 00:06:34,080 this block here inside my compose file. 130 00:06:34,200 --> 00:06:35,360 But what if I want to make a change? 131 00:06:35,560 --> 00:06:37,900 This is where it can get a little bit, I don't know, 132 00:06:38,000 --> 00:06:40,820 like I just expected it to work a different way than what it actually does. 133 00:06:40,980 --> 00:06:41,700 But we'll get there. 134 00:06:42,280 --> 00:06:44,000 Let's make the change to the compose.yaml file. 135 00:06:44,220 --> 00:06:48,660 And I want to change tailscalefunnel to be true for this node, for example. 136 00:06:49,500 --> 00:06:54,060 Now, if I do that and modify the compose file and then do a Docker compose restart, 137 00:06:54,960 --> 00:06:59,099 I've restarted the copyparty and the copyparty tailscale containers, the sidecast. 138 00:06:59,740 --> 00:07:03,980 But if I do the same exec command look, it still says false. 139 00:07:04,660 --> 00:07:09,100 even though our compose file says, our survey says, 140 00:07:09,520 --> 00:07:11,299 even though our compose file says true right here. 141 00:07:11,720 --> 00:07:13,660 It does seem logical that that would work, doesn't it? 142 00:07:13,860 --> 00:07:17,300 But the problem here is that the restart process 143 00:07:17,960 --> 00:07:20,359 only stops the process inside the container 144 00:07:20,820 --> 00:07:22,320 and then starts the container again. 145 00:07:22,360 --> 00:07:24,500 It doesn't run any of the container creation 146 00:07:25,060 --> 00:07:27,800 or instantiation routines that look at the config files 147 00:07:28,400 --> 00:07:29,840 in your YAML file. 148 00:07:30,220 --> 00:07:31,720 It doesn't recompute the hash and say, 149 00:07:31,860 --> 00:07:32,860 hey, something's changed. 150 00:07:33,360 --> 00:07:34,380 We should probably update this. 151 00:07:34,940 --> 00:07:43,980 The container itself will still exist, the same container, the same file system, the same mounted configuration object from when it was first created. 152 00:07:44,700 --> 00:07:50,460 Compose won't go back and read that config file until you recreate the container. 153 00:07:51,280 --> 00:07:53,840 So that leaves us with really only a couple of options. 154 00:07:54,100 --> 00:07:59,560 And the one that I've sort of landed on is to use Docker Compose up force recreate. 155 00:08:00,060 --> 00:08:01,620 So this is a lifecycle issue. 156 00:08:02,300 --> 00:08:06,140 So if we can do a force recreate, then we're all good to go. 157 00:08:06,310 --> 00:08:06,940 So I'm going to do this. 158 00:08:07,030 --> 00:08:11,419 I'm going to do docker compose up minus D dash dash force recreate. 159 00:08:11,830 --> 00:08:14,240 And look what happens to our YAML file. 160 00:08:15,520 --> 00:08:17,740 Our funnel block now reads true. 161 00:08:18,380 --> 00:08:22,680 So if you're using these config blocks to define configs for applications, 162 00:08:23,760 --> 00:08:27,219 you need to understand that they work differently to bind mounts. 163 00:08:27,780 --> 00:08:30,020 They don't update in real time inside the container. 164 00:08:30,420 --> 00:08:36,440 Docker Compose only reads these config blocks at container creation time, at container instantiation time. 165 00:08:37,200 --> 00:08:41,219 So to make a change to those underlying configs and for them to be passed through to the containers, 166 00:08:41,799 --> 00:08:43,839 you just have to recreate the container every time. 167 00:08:44,080 --> 00:08:47,260 It's a small workflow tweak, but a really, really important one. 168 00:08:47,560 --> 00:08:50,239 So that's kind of the gotcha with Docker Compose configs. 169 00:08:51,140 --> 00:08:53,040 They're read once when the container is created, 170 00:08:53,320 --> 00:08:56,700 and then they're locked in until you recreate that container completely. 171 00:08:57,480 --> 00:09:03,200 It's not obvious, and the docs don't exactly make this crystal clear, that whole life cycle of the container piece. 172 00:09:03,460 --> 00:09:08,260 So hopefully this saves some of you from the debugging headache that I had today. 173 00:09:09,220 --> 00:09:15,180 So let me know in the comments what other stuff is hiding in the Docker Compose documentation that I've probably completely missed. 174 00:09:15,760 --> 00:09:18,720 This has been one of my favorite little discoveries of the last few weeks. 175 00:09:19,300 --> 00:09:23,859 Now, I have to say a great big thank you to all of you for watching throughout 2025. 176 00:09:25,360 --> 00:09:30,100 And here is to a prosperous and successful 2026 to all of you. 177 00:09:30,760 --> 00:09:32,140 Thank you so much for watching. 178 00:09:32,560 --> 00:09:33,900 I've been Alex from Tailscale.